diff --git a/CHANGELOG.md b/CHANGELOG.md index 58601987e..d8d57d7c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,34 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.21 (2022-02-10) +# 5.7.22 (2022-02-27) + +### 🐣新特性 +* 【poi 】 ExcelUtil.readBySax增加对POI-5.2.0的兼容性(issue#I4TJF4@gitee) +* 【extra 】 Ftp增加构造(issue#I4TKXP@gitee) +* 【core 】 GenericBuilder支持Map构建(pr#540@Github) +* 【json 】 新增TemporalAccessorSerializer +* 【core 】 使多个xxxBuilder实现Builder接口,扩展CheckedUtil(pr#545@Gitee) +* 【core 】 CheckedUtil删除第二个参数为RuntimeException的方法 +* 【core 】 FileUtil增加getTotalLines方法 +* 【db 】 MetaUtil增加getTableMeta重载(issue#2157@Github) +* 【http 】 增加HttpGlobalConfig.setDecodeUrl(issue#I4U8YQ@Gitee) +* 【core 】 增加Base58(pr#2162@Github) +* 【core 】 增加AntPathMatcher(issue#I4T7K5@Gitee) +* 【core 】 StrJoiner修改toString策略,调用不再修改Appendable +* 【core 】 StrJoiner增加length和merge方法 + +### 🐞Bug修复 +* 【cache 】 修复ReentrantCache.toString方法线程不安全问题(issue#2140@Github) +* 【core 】 修复SystemPropsUtil.getInt返回long问题(pr#546@Gitee) +* 【crypto 】 修复SM2.getD前导0问题(pr#2149@Github) +* 【core 】 修复ChineseDate在1970年之前农历差一天问题(issue#I4UTPK@Gitee) +* 【core 】 修复CoordinateUtil精准问题及转换bug(pr#551@Gitee) +* 【json 】 修复JSONObject解析XML后没有返回的bug(issue#2160@Github) +* 【extra 】 修复GanymedUtil错误信息读取位置问题(issue#I4VDZ2@Gitee) + +------------------------------------------------------------------------------------------------------------- +# 5.7.21 (2022-02-14) ### 🐣新特性 * 【extra 】 增加jetbrick模板支持 @@ -18,12 +45,20 @@ * 【core 】 IntMap和LongMap使用位运算快速求解取余运算(pr#2123@Github) * 【core 】 新增通用builder类:GenericBuilder(pr#526@Gitee) * 【core 】 新增copySafely方法与mkdirsSafely方法(pr#527@Gitee) +* 【core 】 新增MetroHash(pr#532@Gitee) +* 【core 】 SpringUtil增加publishEvent重载(pr#2139@Github) +* 【core 】 DateUtil增加rangeContains、rangeNotContains(pr#537@Gitee) +* 【core 】 Resource增加isModified默认方法 +* 【core 】 增加VfsResource +* 【json 】 JSONConfig增加setKeyComparator、setNatureKeyComparator方法,支持自定义排序(issue#I4RBZ4@Gitee) ### 🐞Bug修复 * 【core 】 修复ChineseDate农历获取正月出现数组越界BUG(issue#2112@Github) * 【extra 】 修复EmojiUtil.toHtmlHex()方法(pr#519@Gitee) * 【system 】 修复CpuInfo.getUsed()方法(issue#2116@Github) * 【dfa 】 修复密集匹配和贪婪匹配冲突问题(issue#2126@Github) +* 【db 】 修复c3p0丢失信息问题(issue#I4T7XZ@Gitee) +* 【http 】 修复Action中HttpExchange没有关闭问题 * 【http 】 修复Action中HttpExchange没有关闭问题 ------------------------------------------------------------------------------------------------------------- diff --git a/README-EN.md b/README-EN.md index 3f9f3cf01..75fcfed12 100644 --- a/README-EN.md +++ b/README-EN.md @@ -142,18 +142,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: cn.hutool hutool-all - 5.7.21 + 5.7.22 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.21' +implementation 'cn.hutool:hutool-all:5.7.22' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.21/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.22/) > 🔔️note: > Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available. diff --git a/README.md b/README.md index 1d8f3f7c9..4516e5e0d 100644 --- a/README.md +++ b/README.md @@ -142,20 +142,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.7.21 + 5.7.22 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.21' +implementation 'cn.hutool:hutool-all:5.7.22' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.21/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.22/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index fcad0d34c..e2ff09a28 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.7.21 +5.7.22 diff --git a/docs/js/version.js b/docs/js/version.js index 6bdf634cd..e637f5f20 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.7.21' \ No newline at end of file +var version = '5.7.22' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 27f161685..a05e7292f 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 94786ad48..c04435114 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-aop @@ -19,7 +19,7 @@ 3.3.0 - 5.3.14 + 5.3.15 diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index e6d5a8a92..1a3d9d895 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index eb557d8b9..6b8f86b02 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 16b19f58b..ea5e65aa9 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cache diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java index 180bc7785..a985ad70b 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java @@ -18,7 +18,7 @@ public abstract class ReentrantCache extends AbstractCache { private static final long serialVersionUID = 1L; // 一些特殊缓存,例如使用了LinkedHashMap的缓存,由于get方法也会改变Map的结构,导致无法使用读写锁 - // 最优的解决方案是使用Guava的ConcurrentLinkedHashMap,此处使用简化的互斥锁 + // TODO 最优的解决方案是使用Guava的ConcurrentLinkedHashMap,此处使用简化的互斥锁 protected final ReentrantLock lock = new ReentrantLock(); @Override @@ -115,6 +115,16 @@ public abstract class ReentrantCache extends AbstractCache { } } + @Override + public String toString() { + lock.lock(); + try { + return super.toString(); + } finally { + lock.unlock(); + } + } + /** * 移除key对应的对象 * diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 61d5e5eb0..856bdb441 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 3331f8bde..0e173df1b 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/GenericBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/GenericBuilder.java index 829ff54db..5d9f0e1f3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/GenericBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/GenericBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.builder; +import cn.hutool.core.lang.func.Consumer3; import cn.hutool.core.lang.func.Supplier1; import cn.hutool.core.lang.func.Supplier2; import cn.hutool.core.lang.func.Supplier3; @@ -45,7 +46,17 @@ import java.util.function.Supplier; * .with(Box::alis) * .build(); * - * 注意:本工具类支持调用的方法的参数数量不超过1个,更多的参数不利于阅读和维护。 + * 还可能这样构建Map对象: + * {@code + * HashMap colorMap = GenericBuilder + * .of(HashMap::new) + * .with(Map::put, "red", "#FF0000") + * .with(Map::put, "yellow", "#FFFF00") + * .with(Map::put, "blue", "#0000FF") + * .build(); + * } + * + * 注意:本工具类支持调用的构造方法的参数数量不超过5个,一般方法的参数数量不超过2个,更多的参数不利于阅读和维护。 * * @author TomXin * @since 5.7.21 @@ -183,9 +194,9 @@ public class GenericBuilder implements Builder { /** * 调用1参数方法 * - * @param 参数一类型 - * @param consumer 1参数Consumer,一般为Setter方法引用 + * @param consumer 1参数Consumer * @param p1 参数一 + * @param 参数一类型 * @return GenericBuilder对象 */ public GenericBuilder with(BiConsumer consumer, P1 p1) { @@ -193,6 +204,21 @@ public class GenericBuilder implements Builder { return this; } + /** + * 调用2参数方法 + * + * @param consumer 2参数Consumer + * @param p1 参数一 + * @param p2 参数二 + * @param 参数一类型 + * @param 参数二类型 + * @return GenericBuilder对象 + */ + public GenericBuilder with(Consumer3 consumer, P1 p1, P2 p2) { + modifiers.add(instant -> consumer.accept(instant, p1, p2)); + return this; + } + /** * 构建 * diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base58.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base58.java new file mode 100644 index 000000000..1a7a6444a --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base58.java @@ -0,0 +1,152 @@ +package cn.hutool.core.codec; + +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.exceptions.ValidateException; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + + +/** + * Base58工具类,提供Base58的编码和解码方案 + * 参考: https://github.com/Anujraval24/Base58Encoding + * 规范见:https://en.bitcoin.it/wiki/Base58Check_encoding + * + * @author lin, looly + * @since 5.7.22 + */ +public class Base58 { + + private static final int CHECKSUM_SIZE = 4; + + // -------------------------------------------------------------------- encode + + /** + * Base58编码 + * 包含版本位和校验位 + * + * @param version 编码版本,{@code null}表示不包含版本位 + * @param data 被编码的数组,添加校验和。 + * @return 编码后的字符串 + */ + public static String encodeChecked(Integer version, byte[] data) { + return encode(addChecksum(version, data)); + } + + /** + * Base58编码 + * + * @param data 被编码的数据,不带校验和。 + * @return 编码后的字符串 + */ + public static String encode(byte[] data) { + return Base58Codec.INSTANCE.encode(data); + } + // -------------------------------------------------------------------- decode + + /** + * Base58解码 + * 解码包含标志位验证和版本呢位去除 + * + * @param encoded 被解码的base58字符串 + * @return 解码后的bytes + * @throws ValidateException 标志位验证错误抛出此异常 + */ + public static byte[] decodeChecked(CharSequence encoded) throws ValidateException { + try { + return decodeChecked(encoded, true); + } catch (ValidateException ignore) { + return decodeChecked(encoded, false); + } + } + + /** + * Base58解码 + * 解码包含标志位验证和版本呢位去除 + * + * @param encoded 被解码的base58字符串 + * @param withVersion 是否包含版本位 + * @return 解码后的bytes + * @throws ValidateException 标志位验证错误抛出此异常 + */ + public static byte[] decodeChecked(CharSequence encoded, boolean withVersion) throws ValidateException { + byte[] valueWithChecksum = decode(encoded); + return verifyAndRemoveChecksum(valueWithChecksum, withVersion); + } + + /** + * Base58解码 + * + * @param encoded 被编码的base58字符串 + * @return 解码后的bytes + */ + public static byte[] decode(CharSequence encoded) { + return Base58Codec.INSTANCE.decode(encoded); + } + + /** + * 验证并去除验证位和版本位 + * + * @param data 编码的数据 + * @param withVersion 是否包含版本位 + * @return 载荷数据 + */ + private static byte[] verifyAndRemoveChecksum(byte[] data, boolean withVersion) { + final byte[] payload = Arrays.copyOfRange(data, withVersion ? 1 : 0, data.length - CHECKSUM_SIZE); + final byte[] checksum = Arrays.copyOfRange(data, data.length - CHECKSUM_SIZE, data.length); + final byte[] expectedChecksum = checksum(payload); + if (false == Arrays.equals(checksum, expectedChecksum)) { + throw new ValidateException("Base58 checksum is invalid"); + } + return payload; + } + + /** + * 数据 + 校验码 + * + * @param version 版本,{@code null}表示不添加版本位 + * @param payload Base58数据(不含校验码) + * @return Base58数据 + */ + private static byte[] addChecksum(Integer version, byte[] payload) { + final byte[] addressBytes; + if (null != version) { + addressBytes = new byte[1 + payload.length + CHECKSUM_SIZE]; + addressBytes[0] = (byte) version.intValue(); + System.arraycopy(payload, 0, addressBytes, 1, payload.length); + } else { + addressBytes = new byte[payload.length + CHECKSUM_SIZE]; + System.arraycopy(payload, 0, addressBytes, 0, payload.length); + } + final byte[] checksum = checksum(payload); + System.arraycopy(checksum, 0, addressBytes, addressBytes.length - CHECKSUM_SIZE, CHECKSUM_SIZE); + return addressBytes; + } + + /** + * 获取校验码 + * 计算规则为对数据进行两次sha256计算,然后取{@link #CHECKSUM_SIZE}长度 + * + * @param data 数据 + * @return 校验码 + */ + private static byte[] checksum(byte[] data) { + byte[] hash = hash256(hash256(data)); + return Arrays.copyOfRange(hash, 0, CHECKSUM_SIZE); + } + + /** + * 计算数据的SHA-256值 + * + * @param data 数据 + * @return sha-256值 + */ + private static byte[] hash256(byte[] data) { + try { + return MessageDigest.getInstance("SHA-256").digest(data); + } catch (NoSuchAlgorithmException e) { + throw new UtilException(e); + } + } +} 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 new file mode 100644 index 000000000..6e6d02d82 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base58Codec.java @@ -0,0 +1,142 @@ +package cn.hutool.core.codec; + +import cn.hutool.core.util.StrUtil; + +import java.util.Arrays; + +/** + * Base58编码器 + * 此编码器不包括校验码、版本等信息 + * + * @author lin, looly + * @since 5.7.22 + */ +public class Base58Codec implements Encoder, Decoder { + + public static Base58Codec INSTANCE = new Base58Codec(); + + private final char[] alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); + private final char ENCODED_ZERO = alphabet[0]; + private final int[] lookup = initLookup(); + + /** + * Base58编码 + * + * @param data 被编码的数据,不带校验和。 + * @return 编码后的字符串 + */ + @Override + public String encode(byte[] data) { + if (null == data) { + return null; + } + if (data.length == 0) { + return StrUtil.EMPTY; + } + // 计算开头0的个数 + int zeroCount = 0; + while (zeroCount < data.length && data[zeroCount] == 0) { + ++zeroCount; + } + // 将256位编码转换为58位编码 + data = Arrays.copyOf(data, data.length); // since we modify it in-place + final char[] encoded = new char[data.length * 2]; // upper bound + int outputStart = encoded.length; + for (int inputStart = zeroCount; inputStart < data.length; ) { + encoded[--outputStart] = alphabet[divmod(data, inputStart, 256, 58)]; + if (data[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input. + while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { + ++outputStart; + } + while (--zeroCount >= 0) { + encoded[--outputStart] = ENCODED_ZERO; + } + // Return encoded string (including encoded leading zeros). + return new String(encoded, outputStart, encoded.length - outputStart); + } + + /** + * 解码给定的Base58字符串 + * + * @param encoded Base58编码字符串 + * @return 解码后的bytes + * @throws IllegalArgumentException 非标准Base58字符串 + */ + @Override + public byte[] decode(CharSequence encoded) throws IllegalArgumentException { + if (encoded.length() == 0) { + return new byte[0]; + } + // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). + final byte[] input58 = new byte[encoded.length()]; + for (int i = 0; i < encoded.length(); ++i) { + char c = encoded.charAt(i); + int digit = c < 128 ? lookup[c] : -1; + if (digit < 0) { + throw new IllegalArgumentException(StrUtil.format("Invalid char '{}' at [{}]", c, i)); + } + input58[i] = (byte) digit; + } + // Count leading zeros. + int zeros = 0; + while (zeros < input58.length && input58[zeros] == 0) { + ++zeros; + } + // Convert base-58 digits to base-256 digits. + byte[] decoded = new byte[encoded.length()]; + int outputStart = decoded.length; + for (int inputStart = zeros; inputStart < input58.length; ) { + decoded[--outputStart] = divmod(input58, inputStart, 58, 256); + if (input58[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Ignore extra leading zeroes that were added during the calculation. + while (outputStart < decoded.length && decoded[outputStart] == 0) { + ++outputStart; + } + // Return decoded data (including original number of leading zeros). + return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length); + } + + /** + * 初始化字符序号查找表 + * + * @return 字符序号查找表 + */ + private int[] initLookup() { + final int[] lookup = new int['z' + 1]; + Arrays.fill(lookup, -1); + for (int i = 0; i < alphabet.length; i++) + lookup[alphabet[i]] = i; + return lookup; + } + + /** + * Divides a number, represented as an array of bytes each containing a single digit + * in the specified base, by the given divisor. The given number is modified in-place + * to contain the quotient, and the return value is the remainder. + * + * @param number the number to divide + * @param firstDigit the index within the array of the first non-zero digit + * (this is used for optimization by skipping the leading zeros) + * @param base the base in which the number's digits are represented (up to 256) + * @param divisor the number to divide by (up to 256) + * @return the remainder of the division operation + */ + private static byte divmod(byte[] number, int firstDigit, int base, int divisor) { + // this is just long division which accounts for the base of the input digits + int remainder = 0; + for (int i = firstDigit; i < number.length; i++) { + int digit = (int) number[i] & 0xFF; + int temp = remainder * base + digit; + number[i] = (byte) (temp / divisor); + remainder = temp % divisor; + } + return (byte) remainder; + } +} 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 0dbc1fadd..2386de005 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,7 +12,7 @@ import java.io.Serializable; * @author Looly, Sebastian Ruhleder, sebastian@seruco.io * @since 4.5.9 */ -public class Base62Codec implements Serializable{ +public class Base62Codec implements Encoder, Decoder, Serializable{ private static final long serialVersionUID = 1L; private static final int STANDARD_BASE = 256; @@ -86,6 +86,7 @@ public class Base62Codec implements Serializable{ * @param message 被编码的消息 * @return Base62内容 */ + @Override public byte[] encode(byte[] message) { final byte[] indices = convert(message, STANDARD_BASE, TARGET_BASE); return translate(indices, alphabet); @@ -97,6 +98,7 @@ public class Base62Codec implements Serializable{ * @param encoded Base62内容 * @return 消息 */ + @Override public byte[] decode(byte[] encoded) { final byte[] prepared = translate(encoded, lookup); return convert(prepared, TARGET_BASE, STANDARD_BASE); @@ -177,4 +179,4 @@ public class Base62Codec implements Serializable{ return (int) Math.ceil((Math.log(sourceBase) / Math.log(targetBase)) * inputLength); } // --------------------------------------------------------------------------------------------------------------- Private method end -} \ No newline at end of file +} 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 91dd741e8..20d0baad9 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 @@ -18,15 +18,6 @@ public class Base64Decoder { private static final byte PADDING = -2; /** Base64解码表,共128位,-1表示非base64字符,-2表示padding */ - // private static final byte[] DECODE_TABLE2 = { - // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - // -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - // 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - // -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - // -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - // 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 }; private static final byte[] DECODE_TABLE = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64Encoder.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64Encoder.java index 608dbc4bf..c1ec7a6d6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64Encoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64Encoder.java @@ -11,7 +11,7 @@ import java.nio.charset.Charset; * @author looly * @since 3.2.0 */ -public class Base64Encoder { +public class Base64Encoder{ private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; /** 标准编码表 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Decoder.java b/hutool-core/src/main/java/cn/hutool/core/codec/Decoder.java new file mode 100644 index 000000000..b1f9e0a57 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Decoder.java @@ -0,0 +1,20 @@ +package cn.hutool.core.codec; + +/** + * 解码接口 + * + * @param 被解码的数据类型 + * @param 解码后的数据类型 + * @author looly + * @since 5.7.22 + */ +public interface Decoder { + + /** + * 执行解码 + * + * @param data 被解码的数据 + * @return 解码后的数据 + */ + R decode(T data); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Encoder.java b/hutool-core/src/main/java/cn/hutool/core/codec/Encoder.java new file mode 100644 index 000000000..ca05d0ac3 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Encoder.java @@ -0,0 +1,20 @@ +package cn.hutool.core.codec; + +/** + * 编码接口 + * + * @param 被编码的数据类型 + * @param 编码后的数据类型 + * @author looly + * @since 5.7.22 + */ +public interface Encoder { + + /** + * 执行编码 + * + * @param encoded 被编码的数据 + * @return 编码后的数据 + */ + R encode(T encoded); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index c538e317c..f03451bc3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -600,18 +600,18 @@ public class IterUtil { /** * 返回{@link Iterator}中第一个匹配规则的值 * - * @param 数组元素类型 - * @param iterator {@link Iterator} - * @param matcher 匹配接口,实现此接口自定义匹配规则 + * @param 数组元素类型 + * @param iterator {@link Iterator} + * @param matcher 匹配接口,实现此接口自定义匹配规则 * @return 匹配元素,如果不存在匹配元素或{@link Iterator}为空,返回 {@code null} * @since 5.7.5 */ public static T firstMatch(Iterator iterator, Matcher matcher) { Assert.notNull(matcher, "Matcher must be not null !"); if (null != iterator) { - while(iterator.hasNext()){ + while (iterator.hasNext()) { final T next = iterator.next(); - if(matcher.match(next)){ + if (matcher.match(next)) { return next; } } @@ -718,7 +718,7 @@ public class IterUtil { * * @param 集合元素类型 * @param iter 集合 - * @param filter 过滤器接口 + * @param filter 过滤器接口,删除{@link Filter#accept(Object)}为{@code false}的元素 * @return 编辑后的集合 * @since 4.6.5 */ @@ -735,6 +735,29 @@ public class IterUtil { return iter; } + /** + * 过滤{@link Iterator}并将过滤后满足条件的元素添加到List中 + * + * @param 元素类型 + * @param iter {@link Iterator} + * @param filter 过滤器,保留{@link Filter#accept(Object)}为{@code true}的元素 + * @return ArrayList + * @since 5.7.22 + */ + public static List filterToList(Iterator iter, Filter filter) { + final List result = new ArrayList<>(); + if (null != iter) { + E ele; + while (iter.hasNext()) { + ele = iter.next(); + if (null == filter || filter.accept(ele)) { + result.add(ele); + } + } + } + return result; + } + /** * Iterator转换为Map,转换规则为: * 按照keyFunc函数规则根据元素对象生成Key,元素作为值 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java index 080b1c419..b0d8bfe12 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java @@ -608,7 +608,7 @@ public class ListUtil { public static void swapTo(List list, T element, Integer targetIndex) { if (CollUtil.isNotEmpty(list)) { final int index = list.indexOf(element); - if (index > 0) { + if (index >= 0) { Collections.swap(list, index, targetIndex); } } @@ -627,7 +627,7 @@ public class ListUtil { public static void swapElement(List list, T element, T targetElement) { if (CollUtil.isNotEmpty(list)) { final int targetIndex = list.indexOf(targetElement); - if (targetIndex > 0) { + if (targetIndex >= 0) { swapTo(list, element, targetIndex); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java index 4ff9948dd..d91516a09 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java @@ -11,6 +11,18 @@ import java.util.function.Function; */ public class CompareUtil { + /** + * 获取自然排序器,即默认排序器 + * + * @param 排序节点类型 + * @return 默认排序器 + * @since 5.7.21 + */ + @SuppressWarnings("unchecked") + public static > Comparator naturalComparator() { + return ComparableComparator.INSTANCE; + } + /** * 对象比较,比较结果取决于comparator,如果被比较对象为null,传入的comparator对象应处理此情况 * 如果传入comparator为null,则使用默认规则比较(此时被比较对象必须实现Comparable接口) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java index 5cac6c295..96b19c52f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java @@ -135,7 +135,7 @@ public class ConverterRegistry implements Serializable { ServiceLoaderUtil.load(Converter.class).forEach(converter -> { try { Type type = TypeUtil.getTypeArgument(ClassUtil.getClass(converter)); - if(null != type){ + if (null != type) { putCustom(type, converter); } } catch (Exception e) { @@ -266,7 +266,6 @@ public class ConverterRegistry implements Serializable { } } - // 特殊类型转换,包括Collection、Map、强转、Array等 final T result = convertSpecial(type, rowType, value, defaultValue); if (null != result) { diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java index 22d5186cf..46c5384e8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java @@ -93,7 +93,7 @@ public class TemporalAccessorConverter extends AbstractConverter rangeContains(DateRange start, DateRange end) { + List startDateTimes = CollUtil.newArrayList((Iterable) start); + List endDateTimes = CollUtil.newArrayList((Iterable) end); + return startDateTimes.stream().filter(endDateTimes::contains).collect(Collectors.toList()); + } + + /** + * 俩个时间区间取差集(end - start) + * + * @param start 开始区间 + * @param end 结束区间 + * @return true 包含 + * @author handy + * @since 5.7.21 + */ + public static List rangeNotContains(DateRange start, DateRange end) { + List startDateTimes = CollUtil.newArrayList((Iterable) start); + List endDateTimes = CollUtil.newArrayList((Iterable) end); + return endDateTimes.stream().filter(item -> !startDateTimes.contains(item)).collect(Collectors.toList()); + } + /** * 按日期范围遍历,执行 function * @@ -1896,7 +1918,7 @@ public class DateUtil extends CalendarUtil { * @param end 结束日期时间 * @param unit 步进单位 * @param func 每次遍历要执行的 function - * @param Date经过函数处理结果类型 + * @param Date经过函数处理结果类型 * @return 结果列表 * @since 5.7.21 */ @@ -1905,7 +1927,7 @@ public class DateUtil extends CalendarUtil { return Collections.emptyList(); } ArrayList list = new ArrayList<>(); - for(DateTime date : range(start, end, unit)){ + for (DateTime date : range(start, end, unit)) { list.add(func.apply(date)); } return list; @@ -1928,7 +1950,7 @@ public class DateUtil extends CalendarUtil { } /** - * 创建日期范围生成器 + * 根据步进单位获取起始日期时间和结束日期时间的时间区间集合 * * @param start 起始日期时间 * @param end 结束日期时间 @@ -1940,7 +1962,7 @@ public class DateUtil extends CalendarUtil { } /** - * 创建日期范围生成器 + * 根据步进单位和步进获取起始日期时间和结束日期时间的时间区间集合 * * @param start 起始日期时间 * @param end 结束日期时间 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java index b065c4ed1..7ca61929a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java @@ -182,6 +182,8 @@ public class LocalDateTimeUtil { if (temporalAccessor instanceof LocalDate) { return ((LocalDate) temporalAccessor).atStartOfDay(); + } else if(temporalAccessor instanceof Instant){ + return LocalDateTime.ofInstant((Instant) temporalAccessor, ZoneId.systemDefault()); } return LocalDateTime.of( @@ -209,6 +211,8 @@ public class LocalDateTimeUtil { if (temporalAccessor instanceof LocalDateTime) { return ((LocalDateTime) temporalAccessor).toLocalDate(); + } else if(temporalAccessor instanceof Instant){ + return of(temporalAccessor).toLocalDate(); } return LocalDate.of( diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java index 034f4868b..067164e0d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java @@ -26,11 +26,12 @@ import java.time.temporal.UnsupportedTemporalTypeException; public class TemporalAccessorUtil extends TemporalUtil{ /** - * 安全获取时间的某个属性,属性不存在返回0 + * 安全获取时间的某个属性,属性不存在返回最小值,一般为0 + * 注意请谨慎使用此方法,某些{@link TemporalAccessor#isSupported(TemporalField)}为{@code false}的方法返回最小值 * * @param temporalAccessor 需要获取的时间对象 * @param field 需要获取的属性 - * @return 时间的值,如果无法获取则默认为 0 + * @return 时间的值,如果无法获取则获取最小值,一般为0 */ public static int get(TemporalAccessor temporalAccessor, TemporalField field) { if (temporalAccessor.isSupported(field)) { diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/GanZhi.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/GanZhi.java index ae9fc1dab..f56ccab97 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/chinese/GanZhi.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/GanZhi.java @@ -75,7 +75,7 @@ public class GanZhi { public static String getGanzhiOfDay(int year, int month, int day) { // 与1970-01-01相差天数,不包括当天 final long days = LocalDate.of(year, month, day).toEpochDay() - 1; - //1899-12-21是农历1899年腊月甲子日 40:相差1900-01-31有40天 - return cyclicalm((int) (days - LunarInfo.BASE_DAY + 40)); + //1899-12-21是农历1899年腊月甲子日 41:相差1900-01-31有41天 + return cyclicalm((int) (days - LunarInfo.BASE_DAY + 41)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java index d558c7a86..faf8fd692 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/LunarInfo.java @@ -1,5 +1,7 @@ package cn.hutool.core.date.chinese; +import java.time.LocalDate; + /** * 阴历(农历)信息 * @@ -13,9 +15,9 @@ public class LunarInfo { */ public static final int BASE_YEAR = 1900; /** - * 1900-01-31 + * 1900-01-31,农历正月初一 */ - public static final long BASE_DAY = -25538; + public static final long BASE_DAY = LocalDate.of(BASE_YEAR, 1, 31).toEpochDay(); /** * 此表来自:https://github.com/jjonline/calendar.js/blob/master/calendar.js diff --git a/hutool-core/src/main/java/cn/hutool/core/exceptions/CheckedUtil.java b/hutool-core/src/main/java/cn/hutool/core/exceptions/CheckedUtil.java index f1380a2d0..ae595168a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/exceptions/CheckedUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/exceptions/CheckedUtil.java @@ -54,7 +54,7 @@ public class CheckedUtil { * @return {@link FuncRt} */ public static FuncRt uncheck(Func expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -66,7 +66,7 @@ public class CheckedUtil { * @return {@link Func0Rt} */ public static Func0Rt uncheck(Func0 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -79,7 +79,7 @@ public class CheckedUtil { * @return {@link Func1Rt} */ public static Func1Rt uncheck(Func1 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } @@ -92,7 +92,7 @@ public class CheckedUtil { * @return {@link VoidFuncRt} */ public static VoidFuncRt uncheck(VoidFunc expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -103,7 +103,7 @@ public class CheckedUtil { * @return {@link VoidFunc0Rt} */ public static VoidFunc0Rt uncheck(VoidFunc0 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -115,107 +115,103 @@ public class CheckedUtil { * @return {@link VoidFunc1Rt} */ public static VoidFunc1Rt uncheck(VoidFunc1 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link FuncRt} */ - public static FuncRt uncheck(Func expression, RuntimeException rte) { + public static FuncRt uncheck(Func expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 最终返回的数据类型 * @return {@link Func0Rt} */ - public static Func0Rt uncheck(Func0 expression, RuntimeException rte) { + public static Func0Rt uncheck(Func0 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return () -> { try { return expression.call(); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link Func1Rt} */ - public static Func1Rt uncheck(Func1 expression, RuntimeException rte) { + public static Func1Rt uncheck(Func1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static VoidFuncRt uncheck(VoidFunc expression, RuntimeException rte) { + public static VoidFuncRt uncheck(VoidFunc expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
注意:本工具类支持调用的方法的参数数量不超过1个,更多的参数不利于阅读和维护。
还可能这样构建Map对象:
注意:本工具类支持调用的构造方法的参数数量不超过5个,一般方法的参数数量不超过2个,更多的参数不利于阅读和维护。
FuncRt
uncheck(Func
expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -66,7 +66,7 @@ public class CheckedUtil { * @return {@link Func0Rt} */ public static Func0Rt uncheck(Func0 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -79,7 +79,7 @@ public class CheckedUtil { * @return {@link Func1Rt} */ public static Func1Rt uncheck(Func1 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } @@ -92,7 +92,7 @@ public class CheckedUtil { * @return {@link VoidFuncRt} */ public static VoidFuncRt uncheck(VoidFunc expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -103,7 +103,7 @@ public class CheckedUtil { * @return {@link VoidFunc0Rt} */ public static VoidFunc0Rt uncheck(VoidFunc0 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -115,107 +115,103 @@ public class CheckedUtil { * @return {@link VoidFunc1Rt} */ public static VoidFunc1Rt uncheck(VoidFunc1 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link FuncRt} */ - public static FuncRt uncheck(Func expression, RuntimeException rte) { + public static FuncRt uncheck(Func expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 最终返回的数据类型 * @return {@link Func0Rt} */ - public static Func0Rt uncheck(Func0 expression, RuntimeException rte) { + public static Func0Rt uncheck(Func0 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return () -> { try { return expression.call(); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link Func1Rt} */ - public static Func1Rt uncheck(Func1 expression, RuntimeException rte) { + public static Func1Rt uncheck(Func1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static VoidFuncRt uncheck(VoidFunc expression, RuntimeException rte) { + public static VoidFuncRt uncheck(VoidFunc expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
Func1Rt
uncheck(Func1
expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } @@ -92,7 +92,7 @@ public class CheckedUtil { * @return {@link VoidFuncRt} */ public static
VoidFuncRt
uncheck(VoidFunc
expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -103,7 +103,7 @@ public class CheckedUtil { * @return {@link VoidFunc0Rt} */ public static VoidFunc0Rt uncheck(VoidFunc0 expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** @@ -115,107 +115,103 @@ public class CheckedUtil { * @return {@link VoidFunc1Rt} */ public static
VoidFunc1Rt
uncheck(VoidFunc1
expression) { - return uncheck(expression, new RuntimeException()); + return uncheck(expression, RuntimeException::new); } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param
运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link FuncRt} */ - public static FuncRt uncheck(Func expression, RuntimeException rte) { + public static FuncRt uncheck(Func expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 最终返回的数据类型 * @return {@link Func0Rt} */ - public static Func0Rt uncheck(Func0 expression, RuntimeException rte) { + public static Func0Rt uncheck(Func0 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return () -> { try { return expression.call(); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link Func1Rt} */ - public static Func1Rt uncheck(Func1 expression, RuntimeException rte) { + public static Func1Rt uncheck(Func1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static VoidFuncRt uncheck(VoidFunc expression, RuntimeException rte) { + public static VoidFuncRt uncheck(VoidFunc expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link FuncRt} */ - public static FuncRt uncheck(Func expression, RuntimeException rte) { + public static FuncRt uncheck(Func expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 最终返回的数据类型 * @return {@link Func0Rt} */ - public static Func0Rt uncheck(Func0 expression, RuntimeException rte) { + public static Func0Rt uncheck(Func0 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return () -> { try { return expression.call(); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link Func1Rt} */ - public static Func1Rt uncheck(Func1 expression, RuntimeException rte) { + public static Func1Rt uncheck(Func1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static VoidFuncRt uncheck(VoidFunc expression, RuntimeException rte) { + public static VoidFuncRt uncheck(VoidFunc expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
expression, RuntimeException rte) { + public static
expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 最终返回的数据类型 * @return {@link Func0Rt} */ - public static Func0Rt uncheck(Func0 expression, RuntimeException rte) { + public static Func0Rt uncheck(Func0 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return () -> { try { return expression.call(); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.Func1的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link Func1Rt} */ - public static Func1Rt uncheck(Func1 expression, RuntimeException rte) { + public static Func1Rt uncheck(Func1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static VoidFuncRt uncheck(VoidFunc expression, RuntimeException rte) { + public static VoidFuncRt uncheck(VoidFunc expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
运行时传入的参数类型 - * @param 最终返回的数据类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link Func1Rt} */ - public static Func1Rt uncheck(Func1 expression, RuntimeException rte) { + public static Func1Rt uncheck(Func1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static VoidFuncRt uncheck(VoidFunc expression, RuntimeException rte) { + public static VoidFuncRt uncheck(VoidFunc expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
运行时传入的参数类型 + * @param 最终返回的数据类型 * @return {@link Func1Rt} */ - public static Func1Rt uncheck(Func1 expression, RuntimeException rte) { + public static Func1Rt uncheck(Func1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static VoidFuncRt uncheck(VoidFunc expression, RuntimeException rte) { + public static VoidFuncRt uncheck(VoidFunc expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { return expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; } /** - * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static VoidFuncRt uncheck(VoidFunc expression, RuntimeException rte) { + public static VoidFuncRt uncheck(VoidFunc expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param
运行时传入的参数类型 * @return {@link VoidFuncRt} */ - public static
expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -246,27 +242,48 @@ public class CheckedUtil { }; } + /** + * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc0的Lambda表达式,和一个可以把Exception转化成RuntimeExceptionde的表达式,当执行表达式抛出任何异常的时候,都会转化成运行时异常 + * 如此一来,代码中就不用显示的try-catch转化成运行时异常 + * + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @return {@link VoidFunc0Rt} + */ + public static VoidFunc0Rt uncheck(VoidFunc0 expression, Supplier1 rteSupplier) { + Objects.requireNonNull(expression, "expression can not be null"); + return () -> { + try { + expression.call(); + } catch (Exception e) { + if (rteSupplier == null) { + throw new RuntimeException(e); + } else { + throw rteSupplier.get(e); + } + } + }; + } /** * 接收一个可以转化成 cn.hutool.core.lang.func.VoidFunc1的Lambda表达式,和一个RuntimeException,当执行表达式抛出任何异常的时候,都会转化成运行时异常 * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * - * @param expression Lambda表达式 - * @param rte 期望抛出的运行时异常 - * @param 运行时传入的参数类型 + * @param expression Lambda表达式 + * @param rteSupplier 转化运行时异常的表达式 + * @param 运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static VoidFunc1Rt uncheck(VoidFunc1 expression, RuntimeException rte) { + public static VoidFunc1Rt uncheck(VoidFunc1 expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
运行时传入的参数类型 * @return {@link VoidFunc1Rt} */ - public static
expression, Supplier1 rteSupplier) { Objects.requireNonNull(expression, "expression can not be null"); return t -> { try { expression.call(t); } catch (Exception e) { - if (rte == null) { + if (rteSupplier == null) { throw new RuntimeException(e); } else { - rte.initCause(e); - throw rte; + throw rteSupplier.get(e); } } }; @@ -274,27 +291,33 @@ public class CheckedUtil { public interface FuncRt extends Func { @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
extends Func
{ @SuppressWarnings("unchecked") + @Override R call(P... parameters) throws RuntimeException; } public interface Func0Rt extends Func0 { + @Override R call() throws RuntimeException; } public interface Func1Rt extends Func1 { + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt extends VoidFunc { @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt extends VoidFunc1 { + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
extends Func1
{ + @Override R call(P parameter) throws RuntimeException; } public interface VoidFuncRt
extends VoidFunc
{ @SuppressWarnings("unchecked") + @Override void call(P... parameters) throws RuntimeException; } public interface VoidFunc0Rt extends VoidFunc0 { + @Override void call() throws RuntimeException; } public interface VoidFunc1Rt
extends VoidFunc1
{ + @Override void call(P parameter) throws RuntimeException; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 72eb18ca6..e0622f7fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -33,6 +33,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.LineNumberReader; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; @@ -41,7 +42,11 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; -import java.nio.file.*; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -567,6 +572,31 @@ public class FileUtil extends PathUtil { } } + /** + * 计算文件的总行数 + * 读取文件采用系统默认编码,一般乱码不会造成行数错误。 + * + * @param file 文件 + * @return 该文件总行数 + * @since 5.7.22 + */ + public static int getTotalLines(File file) { + if (false == isFile(file)) { + throw new IORuntimeException("Input must be a File"); + } + try (final LineNumberReader lineNumberReader = new LineNumberReader(new java.io.FileReader(file))) { + // 设置起始为1 + lineNumberReader.setLineNumber(1); + // 跳过文件中内容 + //noinspection ResultOfMethodCallIgnored + lineNumberReader.skip(Long.MAX_VALUE); + // 获取当前行号 + return lineNumberReader.getLineNumber(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + /** * 给定文件或目录的最后修改时间是否晚于给定时间 * @@ -891,6 +921,54 @@ public class FileUtil extends PathUtil { return createTempFile("hutool", null, dir, true); } + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].tmp。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile() throws IORuntimeException { + return createTempFile("hutool", null, null, true); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile("hutool", suffix, null, isReCreat); + } + + /** + * 在默认临时文件目录下创建临时文件,创建后的文件名为 prefix[Randon].suffix。 + * 默认临时文件目录由系统属性 {@code java.io.tmpdir} 指定。 + * 在 UNIX 系统上,此属性的默认值通常是 {@code "tmp"} 或 {@code "vartmp"}; + * 在 Microsoft Windows 系统上,它通常是 {@code "C:\\WINNT\\TEMP"}。 + * 调用 Java 虚拟机时,可以为该系统属性赋予不同的值,但不保证对该属性的编程更改对该方法使用的临时目录有任何影响。 + * + * @param prefix 前缀,至少3个字符 + * @param suffix 后缀,如果null则使用默认.tmp + * @param isReCreat 是否重新创建文件(删掉原来的,创建新的) + * @return 临时文件 + * @throws IORuntimeException IO异常 + * @since 5.7.22 + */ + public static File createTempFile(String prefix, String suffix, boolean isReCreat) throws IORuntimeException { + return createTempFile(prefix, suffix, null, isReCreat); + } + /** * 创建临时文件 * 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index c6b18ac44..49b7d90ec 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -1,9 +1,10 @@ package cn.hutool.core.io.resource; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -21,7 +22,6 @@ import java.util.List; * Resource资源工具类 * * @author Looly - * */ public class ResourceUtil { @@ -40,7 +40,7 @@ public class ResourceUtil { * 读取Classpath下的资源为字符串 * * @param resource 可以是绝对路径,也可以是相对路径(相对ClassPath) - * @param charset 编码 + * @param charset 编码 * @return 资源内容 * @since 3.1.1 */ @@ -102,7 +102,7 @@ public class ResourceUtil { * 从ClassPath资源中获取{@link BufferedReader} * * @param resource ClassPath资源 - * @param charset 编码 + * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ @@ -139,13 +139,24 @@ public class ResourceUtil { * @return 资源列表 */ public static List getResources(String resource) { - final Enumeration resources; - try { - resources = ClassLoaderUtil.getClassLoader().getResources(resource); - } catch (IOException e) { - throw new IORuntimeException(e); - } - return CollUtil.newArrayList(resources); + return getResources(resource, null); + } + + /** + * 获取指定路径下的资源列表 + * 路径格式必须为目录格式,用/分隔,例如: + * + * + * config/a + * spring/xml + * + * + * @param resource 资源路径 + * @param filter 过滤器,用于过滤不需要的资源,{@code null}表示不过滤,保留所有元素 + * @return 资源列表 + */ + public static List getResources(String resource, Filter filter) { + return IterUtil.filterToList(getResourceIter(resource), filter); } /** @@ -174,7 +185,7 @@ public class ResourceUtil { /** * 获得资源相对路径对应的URL * - * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 + * @param resource 资源相对路径,{@code null}和""都表示classpath根路径 * @param baseClass 基准Class,获得的相对路径相对于此Class所在路径,如果为{@code null}则相对ClassPath * @return {@link URL} */ @@ -192,8 +203,8 @@ public class ResourceUtil { * @since 3.2.1 */ public static Resource getResourceObj(String path) { - if(StrUtil.isNotBlank(path)) { - if(path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { + if (StrUtil.isNotBlank(path)) { + if (path.startsWith(URLUtil.FILE_URL_PREFIX) || FileUtil.isAbsolutePath(path)) { return new FileResource(path); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装 + * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+ + * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class> virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java new file mode 100644 index 000000000..b40a2c387 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Consumer3.java @@ -0,0 +1,23 @@ +package cn.hutool.core.lang.func; + +/** + * 3参数Consumer + * + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.22 + */ +@FunctionalInterface +public interface Consumer3 { + + /** + * 接收参数方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + */ + void accept(P1 p1, P2 p2, P3 p3); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + * + * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
+ * config/a + * spring/xml + *
+ * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap} + * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator super K>) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map extends K, ? extends V> m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap extends K, ? extends V> m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator super K> comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a8409e6d0..5840d9204 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -1,6 +1,8 @@ package cn.hutool.core.map; -import java.io.Serializable; + +import cn.hutool.core.builder.Builder; + import java.util.Map; import java.util.function.Supplier; @@ -11,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Serializable { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -133,6 +135,7 @@ public class MapBuilder implements Serializable { * @return 创建后的map * @since 3.3.0 */ + @Override public Map build() { return map(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java index 65b112564..103c6d45a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/DefaultTrustManager.java @@ -15,7 +15,7 @@ import java.security.cert.X509Certificate; public class DefaultTrustManager extends X509ExtendedTrustManager { /** - * 默认的全局单例默认信任管理器,,默认信任所有客户端和服务端证书 + * 默认的全局单例默认信任管理器,默认信任所有客户端和服务端证书 * @since 5.7.8 */ public static DefaultTrustManager INSTANCE = new DefaultTrustManager(); diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java index 8a0efced2..f7c783dc1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLContextBuilder.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.builder.Builder; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -20,14 +21,15 @@ import java.security.SecureRandom; * {@link TrustManager},默认{@link DefaultTrustManager},即信任全部 * {@link SecureRandom} * - * + * * 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
* 构建后可获得{@link SSLContext},通过调用{@link SSLContext#getSocketFactory()}获取{@link javax.net.ssl.SSLSocketFactory} * * @author Looly * @since 5.5.2 */ -public class SSLContextBuilder implements SSLProtocols { - +public class SSLContextBuilder implements SSLProtocols, Builder { + private static final long serialVersionUID = 1L; + private String protocol = TLS; private KeyManager[] keyManagers; private TrustManager[] trustManagers = {DefaultTrustManager.INSTANCE}; @@ -99,10 +101,21 @@ public class SSLContextBuilder implements SSLProtocols { * 构建{@link SSLContext} * * @return {@link SSLContext} - * @throws NoSuchAlgorithmException 无此算法 - * @throws KeyManagementException Key管理异常 */ - public SSLContext build() throws NoSuchAlgorithmException, KeyManagementException { + @Override + public SSLContext build() { + return buildQuietly(); + } + + /** + * 构建{@link SSLContext}需要处理异常 + * + * @return {@link SSLContext} + * @throws NoSuchAlgorithmException 无此算法异常 + * @throws KeyManagementException 密钥管理异常 + * @since 5.7.22 + */ + public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance(protocol); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); return sslContext; @@ -116,7 +129,7 @@ public class SSLContextBuilder implements SSLProtocols { */ public SSLContext buildQuietly() throws IORuntimeException { try { - return build(); + return buildChecked(); } catch (GeneralSecurityException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java index 8519f94e0..d608467a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/SSLUtil.java @@ -23,7 +23,7 @@ public class SSLUtil { * @since 5.7.8 */ public static SSLContext createSSLContext(String protocol) throws IORuntimeException{ - return SSLContextBuilder.create().setProtocol(protocol).buildQuietly(); + return SSLContextBuilder.create().setProtocol(protocol).build(); } /** @@ -55,6 +55,6 @@ public class SSLUtil { return SSLContextBuilder.create() .setProtocol(protocol) .setKeyManagers(keyManagers) - .setTrustManagers(trustManagers).buildQuietly(); + .setTrustManagers(trustManagers).build(); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 708e9721a..0a9adc1a8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,12 +1,12 @@ package cn.hutool.core.net.url; +import cn.hutool.core.builder.Builder; import cn.hutool.core.lang.Assert; import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import java.io.Serializable; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +26,7 @@ import java.nio.charset.Charset; * @see Uniform Resource Identifier * @since 5.3.1 */ -public final class UrlBuilder implements Serializable { +public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; private static final String DEFAULT_SCHEME = "http"; @@ -468,6 +468,7 @@ public final class UrlBuilder implements Serializable { * * @return URL字符串 */ + @Override public String build() { return toURL().toString(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index e7507503e..46c6de15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -279,6 +279,12 @@ public class UrlQuery { return sb.toString(); } + /** + * 生成查询字符串,类似于aaa=111&bbb=222 + * 此方法不对任何特殊字符编码,仅用于输出显示 + * + * @return 查询字符串 + */ @Override public String toString() { return build(null); diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 8e4001df4..5d9c9d87a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -14,7 +14,6 @@ import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; @@ -114,7 +113,8 @@ public class CollectorUtil { if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { - UnaryOperator downstreamFinisher = (UnaryOperator) downstream.finisher(); + @SuppressWarnings("unchecked") + Function downstreamFinisher = (Function) downstream.finisher(); Function, M> finisher = intermediate -> { intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v)); @SuppressWarnings("unchecked") 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 new file mode 100755 index 000000000..aabc3e97b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -0,0 +1,942 @@ +package cn.hutool.core.text; + + +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Ant风格的路径匹配器。 + * 来自Spring-core和Ant + * + * 匹配URL的规则如下: + * + * {@code ?} 匹配单个字符 + * {@code *} 匹配0个或多个字符 + * {@code **} 0个或多个路径中的目录节点 + * {@code {hutool:[a-z]+}} 匹配以"hutool"命名的正则 {@code [a-z]+} + * + * + * 例子: + * + * {@code com/t?st.jsp} — 匹配 {@code com/test.jsp} 或 {@code com/tast.jsp} 或 {@code com/txst.jsp} + * {@code com/*.jsp} — 匹配{@code com}目录下全部 {@code .jsp}文件 + * {@code com/**/test.jsp} — 匹配{@code com}目录下全部 {@code test.jsp}文件 + * {@code cn/hutool/**/*.jsp} — 匹配{@code cn/hutool}路径下全部{@code .jsp} 文件 + * {@code org/**/servlet/bla.jsp} — 匹配{@code cn/hutool/servlet/bla.jsp} 或{@code cn/hutool/testing/servlet/bla.jsp} 或 {@code org/servlet/bla.jsp} + * {@code com/{filename:\\w+}.jsp} 匹配 {@code com/test.jsp} 并将 {@code test} 关联到 {@code filename} 变量 + * + * + * 注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
匹配URL的规则如下: + *
例子:
注意: 表达式和路径必须都为绝对路径或都为相对路径。 + * + * @author Alef Arendsen, Juergen Hoeller, Rob Harrop, Arjen Poutsma, Rossen Stoyanchev, Sam Brannen, Vladislav Kisel + * @since 5.7.22 + */ +public class AntPathMatcher { + + /** + * Default path separator: "/". + */ + public static final String DEFAULT_PATH_SEPARATOR = StrUtil.SLASH; + + private static final int CACHE_TURNOFF_THRESHOLD = 65536; + + private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}"); + + private static final char[] WILDCARD_CHARS = {'*', '?', '{'}; + + private String pathSeparator; + + private PathSeparatorPatternCache pathSeparatorPatternCache; + + private boolean caseSensitive = true; + + private boolean trimTokens = false; + + private volatile Boolean cachePatterns; + + private final Map tokenizedPatternCache = new ConcurrentHashMap<>(256); + + private final Map stringMatcherCache = new ConcurrentHashMap<>(256); + + + /** + * 使用 {@link #DEFAULT_PATH_SEPARATOR} 作为分隔符构造 + */ + public AntPathMatcher() { + this(DEFAULT_PATH_SEPARATOR); + } + + /** + * 使用自定义的分隔符构造 + * + * @param pathSeparator the path separator to use, must not be {@code null}. + * @since 4.1 + */ + public AntPathMatcher(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + setPathSeparator(pathSeparator); + } + + + /** + * 设置路径分隔符 + * + * @param pathSeparator 分隔符,{@code null}表示使用默认分隔符{@link #DEFAULT_PATH_SEPARATOR} + * @return this + */ + public AntPathMatcher setPathSeparator(String pathSeparator) { + if (null == pathSeparator) { + pathSeparator = DEFAULT_PATH_SEPARATOR; + } + this.pathSeparator = pathSeparator; + this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator); + return this; + } + + /** + * 设置是否大小写敏感,默认为{@code true} + * + * @param caseSensitive 是否大小写敏感 + * @return this + */ + public AntPathMatcher setCaseSensitive(boolean caseSensitive) { + this.caseSensitive = caseSensitive; + return this; + } + + /** + * 设置是否去除路径节点两边的空白符,默认为{@code false} + * + * @param trimTokens 是否去除路径节点两边的空白符 + * @return this + */ + public AntPathMatcher setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + return this; + } + + /** + * Specify whether to cache parsed pattern metadata for patterns passed + * into this matcher's {@link #match} method. A value of {@code true} + * activates an unlimited pattern cache; a value of {@code false} turns + * the pattern cache off completely. + * Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
Default is for the cache to be on, but with the variant to automatically + * turn it off when encountering too many patterns to cache at runtime + * (the threshold is 65536), assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * + * @param cachePatterns 是否缓存表达式 + * @see #getStringMatcher(String) + */ + public AntPathMatcher setCachePatterns(boolean cachePatterns) { + this.cachePatterns = cachePatterns; + return this; + } + + /** + * 判断给定路径是否是表达式 + * + * @param path 路径 + * @return 是否为表达式 + */ + public boolean isPattern(String path) { + if (path == null) { + return false; + } + boolean uriVar = false; + final int length = path.length(); + char c; + for (int i = 0; i < length; i++) { + c = path.charAt(i); + // 含有通配符 + if (c == '*' || c == '?') { + return true; + } + if (c == CharPool.DELIM_START) { + uriVar = true; + continue; + } + if (c == CharPool.DELIM_END && uriVar) { + return true; + } + } + return false; + } + + /** + * 给定路径是否匹配表达式 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean match(String pattern, String path) { + return doMatch(pattern, path, true, null); + } + + /** + * 前置部分匹配 + * + * @param pattern 表达式 + * @param path 路径 + * @return 是否匹配 + */ + public boolean matchStart(String pattern, String path) { + return doMatch(pattern, path, false, null); + } + + /** + * 执行匹配,判断给定的{@code path}是否匹配{@code pattern} + * + * @param pattern 表达式 + * @param path 路径 + * @param fullMatch 是否全匹配。{@code true} 表示全路径匹配,{@code false}表示只匹配开始 + * @param uriTemplateVariables 变量映射 + * @return {@code true} 表示提供的 {@code path} 匹配, {@code false} 表示不匹配 + */ + protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) { + if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { + return false; + } + + final String[] pattDirs = tokenizePattern(pattern); + if (fullMatch && this.caseSensitive && false == isPotentialMatch(path, pattDirs)) { + return false; + } + + final String[] pathDirs = tokenizePath(path); + int pattIdxStart = 0; + int pattIdxEnd = pattDirs.length - 1; + int pathIdxStart = 0; + int pathIdxEnd = pathDirs.length - 1; + + // Match all elements up to the first ** + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxStart]; + if ("**".equals(pattDir)) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) { + return false; + } + pattIdxStart++; + pathIdxStart++; + } + + if (pathIdxStart > pathIdxEnd) { + // Path is exhausted, only match if rest of pattern is * or **'s + if (pattIdxStart > pattIdxEnd) { + return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); + } + if (false == fullMatch) { + return true; + } + if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { + return true; + } + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } else if (pattIdxStart > pattIdxEnd) { + // String not exhausted, but pattern is. Failure. + return false; + } else if (false == fullMatch && "**".equals(pattDirs[pattIdxStart])) { + // Path start definitely matches due to "**" part in pattern. + return true; + } + + // up to last '**' + while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { + String pattDir = pattDirs[pattIdxEnd]; + if (pattDir.equals("**")) { + break; + } + if (notMatchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { + return false; + } + pattIdxEnd--; + pathIdxEnd--; + } + if (pathIdxStart > pathIdxEnd) { + // String is exhausted + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + return true; + } + + while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { + int patIdxTmp = -1; + for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { + if (pattDirs[i].equals("**")) { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == pattIdxStart + 1) { + // '**/**' situation, so skip one + pattIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - pattIdxStart - 1); + int strLength = (pathIdxEnd - pathIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = pattDirs[pattIdxStart + j + 1]; + String subStr = pathDirs[pathIdxStart + i + j]; + if (notMatchStrings(subPat, subStr, uriTemplateVariables)) { + continue strLoop; + } + } + foundIdx = pathIdxStart + i; + break; + } + + if (foundIdx == -1) { + return false; + } + + pattIdxStart = patIdxTmp; + pathIdxStart = foundIdx + patLength; + } + + for (int i = pattIdxStart; i <= pattIdxEnd; i++) { + if (false == pattDirs[i].equals("**")) { + return false; + } + } + + return true; + } + + private boolean isPotentialMatch(String path, String[] pattDirs) { + if (!this.trimTokens) { + int pos = 0; + for (String pattDir : pattDirs) { + int skipped = skipSeparator(path, pos, this.pathSeparator); + pos += skipped; + skipped = skipSegment(path, pos, pattDir); + if (skipped < pattDir.length()) { + return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0)))); + } + pos += skipped; + } + } + return true; + } + + private int skipSegment(String path, int pos, String prefix) { + int skipped = 0; + for (int i = 0; i < prefix.length(); i++) { + char c = prefix.charAt(i); + if (isWildcardChar(c)) { + return skipped; + } + int currPos = pos + skipped; + if (currPos >= path.length()) { + return 0; + } + if (c == path.charAt(currPos)) { + skipped++; + } + } + return skipped; + } + + private int skipSeparator(String path, int pos, String separator) { + int skipped = 0; + while (path.startsWith(separator, pos + skipped)) { + skipped += separator.length(); + } + return skipped; + } + + private boolean isWildcardChar(char c) { + for (char candidate : WILDCARD_CHARS) { + if (c == candidate) { + return true; + } + } + return false; + } + + /** + * Tokenize the given path pattern into parts, based on this matcher's settings. + * Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
Performs caching based on {@link #setCachePatterns}, delegating to + * {@link #tokenizePath(String)} for the actual tokenization algorithm. + * + * @param pattern the pattern to tokenize + * @return the tokenized pattern parts + */ + protected String[] tokenizePattern(String pattern) { + String[] tokenized = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + tokenized = this.tokenizedPatternCache.get(pattern); + } + if (tokenized == null) { + tokenized = tokenizePath(pattern); + if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return tokenized; + } + if (cachePatterns == null || cachePatterns) { + this.tokenizedPatternCache.put(pattern, tokenized); + } + } + return tokenized; + } + + private void deactivatePatternCache() { + this.cachePatterns = false; + this.tokenizedPatternCache.clear(); + this.stringMatcherCache.clear(); + } + + /** + * Tokenize the given path into parts, based on this matcher's settings. + * + * @param path the path to tokenize + * @return the tokenized path parts + */ + protected String[] tokenizePath(String path) { + return StrSplitter.splitToArray(path, this.pathSeparator, 0, this.trimTokens, true); + } + + /** + * Test whether or not a string matches against a pattern. + * + * @param pattern the pattern to match against (never {@code null}) + * @param str the String which must be matched against the pattern (never {@code null}) + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise + */ + private boolean notMatchStrings(String pattern, String str, Map uriTemplateVariables) { + return false == getStringMatcher(pattern).matchStrings(str, uriTemplateVariables); + } + + /** + * Build or retrieve an {@link AntPathStringMatcher} for the given pattern. + * The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + * When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + * This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part. For example: + * '{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} → '' + * '{@code /docs/*}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} → '{@code commit.html}' + * '{@code /docs/**}' and '{@code /docs/cvs/commit} → '{@code cvs/commit}' + * '{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} → '{@code cvs/commit.html}' + * '{@code /*.html}' and '{@code /docs/cvs/commit.html} → '{@code docs/cvs/commit.html}' + * '{@code *.html}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * '{@code *}' and '{@code /docs/cvs/commit.html} → '{@code /docs/cvs/commit.html}' + * Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
The default implementation checks this AntPathMatcher's internal cache + * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance + * if no cached copy is found. + *
When encountering too many patterns to cache at runtime (the threshold is 65536), + * it turns the default cache off, assuming that arbitrary permutations of patterns + * are coming in, with little chance for encountering a recurring pattern. + *
This method may be overridden to implement a custom cache strategy. + * + * @param pattern the pattern to match against (never {@code null}) + * @return a corresponding AntPathStringMatcher (never {@code null}) + * @see #setCachePatterns + */ + protected AntPathStringMatcher getStringMatcher(String pattern) { + AntPathStringMatcher matcher = null; + Boolean cachePatterns = this.cachePatterns; + if (cachePatterns == null || cachePatterns) { + matcher = this.stringMatcherCache.get(pattern); + } + if (matcher == null) { + matcher = new AntPathStringMatcher(pattern, this.caseSensitive); + if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) { + // Try to adapt to the runtime situation that we're encountering: + // There are obviously too many different patterns coming in here... + // So let's turn off the cache since the patterns are unlikely to be reoccurring. + deactivatePatternCache(); + return matcher; + } + if (cachePatterns == null || cachePatterns) { + this.stringMatcherCache.put(pattern, matcher); + } + } + return matcher; + } + + /** + * Given a pattern and a full path, determine the pattern-mapped part.
For example:
Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but + * does not enforce this. + * + * @param pattern 表达式 + * @param path 路径 + * @return 表达式匹配到的部分 + */ + public String extractPathWithinPattern(String pattern, String path) { + String[] patternParts = tokenizePath(pattern); + String[] pathParts = tokenizePath(path); + StringBuilder builder = new StringBuilder(); + boolean pathStarted = false; + + for (int segment = 0; segment < patternParts.length; segment++) { + String patternPart = patternParts[segment]; + if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) { + for (; segment < pathParts.length; segment++) { + if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) { + builder.append(this.pathSeparator); + } + builder.append(pathParts[segment]); + pathStarted = true; + } + } + } + + return builder.toString(); + } + + public Map extractUriTemplateVariables(String pattern, String path) { + Map variables = new LinkedHashMap<>(); + boolean result = doMatch(pattern, path, true, variables); + if (!result) { + throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); + } + return variables; + } + + /** + * Combine two patterns into a new pattern. + * This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + * Examples + * + * Pattern 1Pattern 2Result + * {@code null}{@code null} + * /hotels{@code null}/hotels + * {@code null}/hotels/hotels + * /hotels/bookings/hotels/bookings + * /hotelsbookings/hotels/bookings + * /hotels/*/bookings/hotels/bookings + * /hotels/**/bookings/hotels/**/bookings + * /hotels{hotel}/hotels/{hotel} + * /hotels/*{hotel}/hotels/{hotel} + * /hotels/**{hotel}/hotels/**/{hotel} + * /*.html/hotels.html/hotels.html + * /*.html/hotels/hotels.html + * /*.html/*.txt{@code IllegalArgumentException} + * + * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * @return the combination of the two patterns + * @throws IllegalArgumentException if the two patterns cannot be combined + */ + public String combine(String pattern1, String pattern2) { + if (StrUtil.isEmpty(pattern1) && StrUtil.isEmpty(pattern2)) { + return StrUtil.EMPTY; + } + if (StrUtil.isEmpty(pattern1)) { + return pattern2; + } + if (StrUtil.isEmpty(pattern2)) { + return pattern1; + } + + boolean pattern1ContainsUriVar = (pattern1.indexOf('{') != -1); + if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) { + // /* + /hotel -> /hotel ; "/*.*" + "/*.html" -> /*.html + // However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar + return pattern2; + } + + // /hotels/* + /booking -> /hotels/booking + // /hotels/* + booking -> /hotels/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) { + return concat(pattern1.substring(0, pattern1.length() - 2), pattern2); + } + + // /hotels/** + /booking -> /hotels/**/booking + // /hotels/** + booking -> /hotels/**/booking + if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) { + return concat(pattern1, pattern2); + } + + int starDotPos1 = pattern1.indexOf("*."); + if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) { + // simply concatenate the two patterns + return concat(pattern1, pattern2); + } + + String ext1 = pattern1.substring(starDotPos1 + 1); + int dotPos2 = pattern2.indexOf('.'); + String file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2)); + String ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2)); + boolean ext1All = (ext1.equals(".*") || ext1.isEmpty()); + boolean ext2All = (ext2.equals(".*") || ext2.isEmpty()); + if (!ext1All && !ext2All) { + throw new IllegalArgumentException("Cannot combine patterns: " + pattern1 + " vs " + pattern2); + } + String ext = (ext1All ? ext2 : ext1); + return file2 + ext; + } + + private String concat(String path1, String path2) { + boolean path1EndsWithSeparator = path1.endsWith(this.pathSeparator); + boolean path2StartsWithSeparator = path2.startsWith(this.pathSeparator); + + if (path1EndsWithSeparator && path2StartsWithSeparator) { + return path1 + path2.substring(1); + } else if (path1EndsWithSeparator || path2StartsWithSeparator) { + return path1 + path2; + } else { + return path1 + this.pathSeparator + path2; + } + } + + /** + * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of + * explicitness. + * This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + * + * {@code /hotels/new} + * {@code /hotels/{hotel}} + * {@code /hotels/*} + * + * The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
This implementation simply concatenates the two patterns, unless + * the first pattern contains a file extension match (e.g., {@code *.html}). + * In that case, the second pattern will be merged into the first. Otherwise, + * an {@code IllegalArgumentException} will be thrown. + *
Examples
This {@code Comparator} will {@linkplain List#sort(Comparator) sort} + * a list so that more specific patterns (without URI templates or wild cards) come before + * generic patterns. So given a list with the following patterns, the returned comparator + * will sort this list so that the order will be as indicated. + *
The full path given as parameter is used to test for exact matches. So when the given path + * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. + * + * @param path the full path to use for comparison + * @return a comparator capable of sorting patterns in order of explicitness + */ + public Comparator getPatternComparator(String path) { + return new AntPatternComparator(path); + } + + + /** + * Tests whether or not a string matches against a pattern via a {@link Pattern}. + * The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
The pattern may contain special characters: '*' means zero or more characters; '?' means one and + * only one character; '{' and '}' indicate a URI template pattern. For example /users/{user}. + */ + protected static class AntPathStringMatcher { + + private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}"); + + private static final String DEFAULT_VARIABLE_PATTERN = "((?s).*)"; + + private final String rawPattern; + + private final boolean caseSensitive; + + private final boolean exactMatch; + + private final Pattern pattern; + + private final List variableNames = new ArrayList<>(); + + public AntPathStringMatcher(String pattern, boolean caseSensitive) { + this.rawPattern = pattern; + this.caseSensitive = caseSensitive; + StringBuilder patternBuilder = new StringBuilder(); + Matcher matcher = GLOB_PATTERN.matcher(pattern); + int end = 0; + while (matcher.find()) { + patternBuilder.append(quote(pattern, end, matcher.start())); + String match = matcher.group(); + if ("?".equals(match)) { + patternBuilder.append('.'); + } else if ("*".equals(match)) { + patternBuilder.append(".*"); + } else if (match.startsWith("{") && match.endsWith("}")) { + int colonIdx = match.indexOf(':'); + if (colonIdx == -1) { + patternBuilder.append(DEFAULT_VARIABLE_PATTERN); + this.variableNames.add(matcher.group(1)); + } else { + String variablePattern = match.substring(colonIdx + 1, match.length() - 1); + patternBuilder.append('('); + patternBuilder.append(variablePattern); + patternBuilder.append(')'); + String variableName = match.substring(1, colonIdx); + this.variableNames.add(variableName); + } + } + end = matcher.end(); + } + // No glob pattern was found, this is an exact String match + if (end == 0) { + this.exactMatch = true; + this.pattern = null; + } else { + this.exactMatch = false; + patternBuilder.append(quote(pattern, end, pattern.length())); + this.pattern = (this.caseSensitive ? Pattern.compile(patternBuilder.toString()) : + Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE)); + } + } + + private String quote(String s, int start, int end) { + if (start == end) { + return ""; + } + return Pattern.quote(s.substring(start, end)); + } + + /** + * Main entry point. + * + * @return {@code true} if the string matches against the pattern, or {@code false} otherwise. + */ + public boolean matchStrings(String str, Map uriTemplateVariables) { + if (this.exactMatch) { + return this.caseSensitive ? this.rawPattern.equals(str) : this.rawPattern.equalsIgnoreCase(str); + } else if (this.pattern != null) { + Matcher matcher = this.pattern.matcher(str); + if (matcher.matches()) { + if (uriTemplateVariables != null) { + if (this.variableNames.size() != matcher.groupCount()) { + throw new IllegalArgumentException("The number of capturing groups in the pattern segment " + + this.pattern + " does not match the number of URI template variables it defines, " + + "which can occur if capturing groups are used in a URI template regex. " + + "Use non-capturing groups instead."); + } + for (int i = 1; i <= matcher.groupCount(); i++) { + String name = this.variableNames.get(i - 1); + if (name.startsWith("*")) { + throw new IllegalArgumentException("Capturing patterns (" + name + ") are not " + + "supported by the AntPathMatcher. Use the PathPatternParser instead."); + } + String value = matcher.group(i); + uriTemplateVariables.put(name, value); + } + } + return true; + } + } + return false; + } + + } + + + /** + * The default {@link Comparator} implementation returned by + * {@link #getPatternComparator(String)}. + * In order, the most "generic" pattern is determined by the following: + * + * if it's null or a capture all pattern (i.e. it is equal to "/**") + * if the other pattern is an actual match + * if it's a catch-all pattern (i.e. it ends with "**" + * if it's got more "*" than the other pattern + * if it's got more "{foo}" than the other pattern + * if it's shorter than the other pattern + * + */ + protected static class AntPatternComparator implements Comparator { + + private final String path; + + public AntPatternComparator(String path) { + this.path = path; + } + + /** + * Compare two patterns to determine which should match first, i.e. which + * is the most specific regarding the current path. + * + * @param pattern1 表达式1 + * @param pattern2 表达式2 + * @return a negative integer, zero, or a positive integer as pattern1 is + * more specific, equally specific, or less specific than pattern2. + */ + @Override + public int compare(String pattern1, String pattern2) { + PatternInfo info1 = new PatternInfo(pattern1); + PatternInfo info2 = new PatternInfo(pattern2); + + if (info1.isLeastSpecific() && info2.isLeastSpecific()) { + return 0; + } else if (info1.isLeastSpecific()) { + return 1; + } else if (info2.isLeastSpecific()) { + return -1; + } + + boolean pattern1EqualsPath = pattern1.equals(this.path); + boolean pattern2EqualsPath = pattern2.equals(this.path); + if (pattern1EqualsPath && pattern2EqualsPath) { + return 0; + } else if (pattern1EqualsPath) { + return -1; + } else if (pattern2EqualsPath) { + return 1; + } + + if (info1.isPrefixPattern() && info2.isPrefixPattern()) { + return info2.getLength() - info1.getLength(); + } else if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) { + return 1; + } else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) { + return -1; + } + + if (info1.getTotalCount() != info2.getTotalCount()) { + return info1.getTotalCount() - info2.getTotalCount(); + } + + if (info1.getLength() != info2.getLength()) { + return info2.getLength() - info1.getLength(); + } + + if (info1.getSingleWildcards() < info2.getSingleWildcards()) { + return -1; + } else if (info2.getSingleWildcards() < info1.getSingleWildcards()) { + return 1; + } + + if (info1.getUriVars() < info2.getUriVars()) { + return -1; + } else if (info2.getUriVars() < info1.getUriVars()) { + return 1; + } + + return 0; + } + + + /** + * Value class that holds information about the pattern, e.g. number of + * occurrences of "*", "**", and "{" pattern elements. + */ + private static class PatternInfo { + + private final String pattern; + private int uriVars; + private int singleWildcards; + private int doubleWildcards; + private boolean catchAllPattern; + private boolean prefixPattern; + private Integer length; + + public PatternInfo(String pattern) { + this.pattern = pattern; + if (this.pattern != null) { + initCounters(); + this.catchAllPattern = this.pattern.equals("/**"); + this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**"); + } + if (this.uriVars == 0) { + this.length = (this.pattern != null ? this.pattern.length() : 0); + } + } + + protected void initCounters() { + int pos = 0; + if (this.pattern != null) { + while (pos < this.pattern.length()) { + if (this.pattern.charAt(pos) == '{') { + this.uriVars++; + pos++; + } else if (this.pattern.charAt(pos) == '*') { + if (pos + 1 < this.pattern.length() && this.pattern.charAt(pos + 1) == '*') { + this.doubleWildcards++; + pos += 2; + } else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { + this.singleWildcards++; + pos++; + } else { + pos++; + } + } else { + pos++; + } + } + } + } + + public int getUriVars() { + return this.uriVars; + } + + public int getSingleWildcards() { + return this.singleWildcards; + } + + public int getDoubleWildcards() { + return this.doubleWildcards; + } + + public boolean isLeastSpecific() { + return (this.pattern == null || this.catchAllPattern); + } + + public boolean isPrefixPattern() { + return this.prefixPattern; + } + + public int getTotalCount() { + return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards); + } + + /** + * Returns the length of the given pattern, where template variables are considered to be 1 long. + * + * @return 长度 + */ + public int getLength() { + if (this.length == null) { + this.length = (this.pattern != null ? + VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length() : 0); + } + return this.length; + } + } + } + + + /** + * A simple cache for patterns that depend on the configured path separator. + */ + private static class PathSeparatorPatternCache { + + private final String endsOnWildCard; + + private final String endsOnDoubleWildCard; + + public PathSeparatorPatternCache(String pathSeparator) { + this.endsOnWildCard = pathSeparator + "*"; + this.endsOnDoubleWildCard = pathSeparator + "**"; + } + + public String getEndsOnWildCard() { + return this.endsOnWildCard; + } + + public String getEndsOnDoubleWildCard() { + return this.endsOnDoubleWildCard; + } + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java index bfebd8703..e2a8a5387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrJoiner.java @@ -12,7 +12,14 @@ import java.util.Iterator; import java.util.function.Function; /** - * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 字符串连接器(拼接器),通过给定的字符串和多个元素,拼接为一个字符串 + * 相较于{@link java.util.StringJoiner}提供更加灵活的配置,包括: + * + * 支持任意Appendable接口实现 + * 支持每个元素单独wrap + * 支持自定义null的处理逻辑 + * 支持自定义默认结果 + * * * @author looly * @since 5.7.2 @@ -282,6 +289,11 @@ public class StrJoiner implements Appendable, Serializable { @Override public StrJoiner append(CharSequence csq) { + return append(csq, 0, csq.length()); + } + + @Override + public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { if (null == csq) { switch (this.nullMode) { case IGNORE: @@ -298,7 +310,7 @@ public class StrJoiner implements Appendable, Serializable { if (wrapElement && StrUtil.isNotEmpty(this.prefix)) { appendable.append(prefix); } - appendable.append(csq); + appendable.append(csq, startInclude, endExclude); if (wrapElement && StrUtil.isNotEmpty(this.suffix)) { appendable.append(suffix); } @@ -308,29 +320,55 @@ public class StrJoiner implements Appendable, Serializable { return this; } - @Override - public StrJoiner append(CharSequence csq, int startInclude, int endExclude) { - return append(StrUtil.sub(csq, startInclude, endExclude)); - } - @Override public StrJoiner append(char c) { return append(String.valueOf(c)); } + /** + * 合并一个StrJoiner 到当前的StrJoiner + * 合并规则为,在尾部直接追加,当存在{@link #prefix}时,如果{@link #wrapElement}为{@code false},则去除之。 + * + * @param strJoiner 其他的StrJoiner + * @return this + * @since 5.7.22 + */ + public StrJoiner merge(StrJoiner strJoiner){ + if(null != strJoiner && null != strJoiner.appendable){ + final String otherStr = strJoiner.toString(); + if(strJoiner.wrapElement){ + this.append(otherStr); + }else{ + this.append(otherStr, this.prefix.length(), otherStr.length()); + } + } + return this; + } + + /** + * 长度 + * 长度计算方式为prefix + suffix + content + * 此方法结果与toString().length()一致。 + * + * @return 长度,如果结果为{@code null},返回-1 + * @since 5.7.22 + */ + public int length() { + return (this.appendable != null ? this.appendable.toString().length() + suffix.length() : + null == this.emptyResult ? -1 : emptyResult.length()); + } + @Override public String toString() { if (null == this.appendable) { return emptyResult; } + + String result = this.appendable.toString(); if (false == wrapElement && StrUtil.isNotEmpty(this.suffix)) { - try { - this.appendable.append(this.suffix); - } catch (IOException e) { - throw new IORuntimeException(e); - } + result += this.suffix; } - return this.appendable.toString(); + return result; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil + * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..d2517a855 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,30 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值 + * 自定义端序 + * + * @param bytes byte数组 + * @param start 开始位置(包含) + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +261,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long + * 自定义端序 + * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java index 826d711eb..f915358ad 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CoordinateUtil.java @@ -22,12 +22,12 @@ public class CoordinateUtil { /** * 坐标转换参数:(火星坐标系与百度坐标系转换的中间量) */ - public static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + public static final double X_PI = 3.1415926535897932384626433832795 * 3000.0 / 180.0; /** * 坐标转换参数:π */ - public static final double PI = 3.1415926535897932384626D; + public static final double PI = 3.1415926535897932384626433832795D; /** * 地球半径(Krasovsky 1940) @@ -280,7 +280,7 @@ public class CoordinateUtil { */ public Coordinate offset(Coordinate offset){ this.lng += offset.lng; - this.lat += offset.lng; + this.lat += offset.lat; return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java index 2cdbe7ef9..e1ee0bf26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -7,7 +7,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; /** - * 统一社会信用代码工具类 + * 统一社会信用代码(GB32100-2015)工具类 + * 标准见:https://www.cods.org.cn/c/2020-10-29/12575.html * * * 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
In order, the most "generic" pattern is determined by the following: + *
* 第一部分:登记管理部门代码1位 (数字或大写英文字母) @@ -24,7 +25,13 @@ public class CreditCodeUtil { public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + /** + * 加权因子 + */ private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + /** + * 代码字符集 + */ private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); private static final Map CODE_INDEX_MAP; @@ -110,10 +117,10 @@ public class CreditCodeUtil { } /** - * 获取校验码 + * 获取校验位的值 * * @param creditCode 统一社会信息代码 - * @return 获取较验位的值 + * @return 获取校验位的值,-1表示获取错误 */ private static int getParityBit(CharSequence creditCode) { int sum = 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java index e9212367f..3336445bb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java @@ -105,7 +105,7 @@ public class ReUtil { } /** - * 获得匹配的字符串,,获得正则中分组0的内容 + * 获得匹配的字符串,获得正则中分组0的内容 * * @param pattern 编译后的正则模式 * @param content 被匹配的内容 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index d5fc4c9f5..b1b030c28 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -34,7 +34,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -58,7 +58,7 @@ public class RuntimeUtil { } /** - * 执行系统命令,使用系统默认编码 + * 执行系统命令,使用传入的 {@link Charset charset} 编码 * * @param charset 编码 * @param cmds 命令列表,每个元素代表一条命令 @@ -131,7 +131,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,获取后销毁进程 + * 获取命令执行结果,使用传入的 {@link Charset charset} 编码,获取后销毁进程 * * @param process {@link Process} 进程 * @param charset 编码 @@ -150,7 +150,7 @@ public class RuntimeUtil { } /** - * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 @@ -180,7 +180,7 @@ public class RuntimeUtil { } /** - * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 + * 获取命令执行异常结果,使用系统默认编码,获取后销毁进程 * * @param process {@link Process} 进程 * @return 命令执行结果列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java index d22679567..83d9d1cf7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -108,7 +108,7 @@ public class SystemPropsUtil { * @param defaultValue 默认值 * @return 值 */ - public static long getInt(String key, int defaultValue) { + public static int getInt(String key, int defaultValue) { return Convert.toInt(get(key), defaultValue); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index 3b1ac5655..84657cfd1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.compress.Deflate; import cn.hutool.core.compress.Gzip; import cn.hutool.core.compress.ZipCopyVisitor; @@ -29,7 +30,6 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.zip.ZipEntry; @@ -953,7 +953,8 @@ public class ZipUtil { } /** - * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 获取Zip文件中指定目录下的所有文件,只显示文件,不显示目录 + * 此方法并不会关闭{@link ZipFile}。 * * @param zipFile Zip文件 * @param dir 目录前缀(目录前缀不包含开头的/) @@ -968,7 +969,7 @@ public class ZipUtil { final List fileNames = new ArrayList<>(); String name; - for (ZipEntry entry : Collections.list(zipFile.entries())) { + for (ZipEntry entry : new EnumerationIter<>(zipFile.entries())) { name = entry.getName(); if (StrUtil.isEmpty(dir) || name.startsWith(dir)) { final String nameSuffix = StrUtil.removePrefix(name, dir); diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java index 84f6d4581..e74a985ac 100644 --- a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -8,6 +8,9 @@ import lombok.experimental.Accessors; import org.junit.Assert; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + /** * {@link GenericBuilder} 单元测试类 * @@ -58,6 +61,21 @@ public class GenericBuilderTest { Assert.assertEquals(222, box1.getLength().intValue()); Assert.assertEquals(333, box1.getWidth().intValue()); Assert.assertEquals(444, box1.getHeight().intValue()); + Assert.assertEquals("TomXin:\"Hello Partner!\"", box1.getTitleAlias()); + } + + @Test + public void buildMapTest(){ + //Map创建 + HashMap colorMap = GenericBuilder + .of(HashMap::new) + .with(Map::put, "red", "#FF0000") + .with(Map::put, "yellow", "#FFFF00") + .with(Map::put, "blue", "#0000FF") + .build(); + Assert.assertEquals("#FF0000", colorMap.get("red")); + Assert.assertEquals("#FFFF00", colorMap.get("yellow")); + Assert.assertEquals("#0000FF", colorMap.get("blue")); } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java new file mode 100644 index 000000000..0dab5faf2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base58Test.java @@ -0,0 +1,40 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +public class Base58Test { + + @Test + public void encodeCheckedTest() { + String a = "hello world"; + String encode = Base58.encodeChecked(0, a.getBytes()); + Assert.assertEquals(1 + "3vQB7B6MrGQZaxCuFg4oh", encode); + + // 无版本位 + encode = Base58.encodeChecked(null, a.getBytes()); + Assert.assertEquals("3vQB7B6MrGQZaxCuFg4oh", encode); + } + @Test + public void encodeTest() { + String a = "hello world"; + String encode = Base58.encode(a.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals("StV1DL6CwTryKyV", encode); + } + @Test + public void decodeCheckedTest() { + String a = "3vQB7B6MrGQZaxCuFg4oh"; + byte[] decode = Base58.decodeChecked(1 + a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + decode = Base58.decodeChecked(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } + @Test + public void testDecode() { + String a = "StV1DL6CwTryKyV"; + byte[] decode = Base58.decode(a); + Assert.assertArrayEquals("hello world".getBytes(StandardCharsets.UTF_8),decode); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 5cecd5de3..937fb5917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -227,5 +227,9 @@ public class ListUtilTest { ListUtil.swapElement(list, map2, map3); Map map = list.get(2); Assert.assertEquals("李四", map.get("2")); + + ListUtil.swapElement(list, map2, map1); + map = list.get(0); + Assert.assertEquals("李四", map.get("2")); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index e2d86d1e9..20914552c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -4,6 +4,8 @@ import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Date; + public class ChineseDateTest { @Test @@ -113,4 +115,28 @@ public class ChineseDateTest { final String chineseMonth = springFestival.getChineseMonth(); Assert.assertEquals("一月", chineseMonth); } + + @Test + public void day19700101Test(){ + // https://gitee.com/dromara/hutool/issues/I4UTPK + Date date = DateUtil.parse("1970-01-01"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿四", chineseDate.toString()); + + date = DateUtil.parse("1970-01-02"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿五", chineseDate.toString()); + + date = DateUtil.parse("1970-01-03"); + chineseDate = new ChineseDate(date); + Assert.assertEquals("己酉鸡年 冬月廿六", chineseDate.toString()); + } + + @Test + public void day19000101Test(){ + // 1900-01-31之前不支持 + Date date = DateUtil.parse("1900-01-31"); + ChineseDate chineseDate = new ChineseDate(date); + Assert.assertEquals("庚子鼠年 正月初一", chineseDate.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index b75ed7737..fc7e6d8e7 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -692,7 +692,6 @@ public class DateUtilTest { @Test public void parseCSTTest() { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; - Console.log(TimeZone.getDefault().getDisplayName()); SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 @@ -995,11 +994,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1022,22 +1023,8 @@ public class DateUtilTest { } @Test - public void isOverlapTest() { - DateTime oneStartTime = DateTime.of("2022-01-01 10:10:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime = DateTime.of("2022-01-01 11:10:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime2 = DateTime.of("2022-01-01 11:20:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime2 = DateTime.of("2022-01-01 11:30:10", DatePattern.NORM_DATETIME_PATTERN); - - DateTime oneStartTime3 = DateTime.of("2022-01-01 11:40:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime oneEndTime3 = DateTime.of("2022-01-01 11:50:10", DatePattern.NORM_DATETIME_PATTERN); - - //真实请假数据 - DateTime realStartTime = DateTime.of("2022-01-01 11:49:10", DatePattern.NORM_DATETIME_PATTERN); - DateTime realEndTime = DateTime.of("2022-01-01 12:00:10", DatePattern.NORM_DATETIME_PATTERN); - - Assert.assertTrue(DateUtil.isOverlap(oneStartTime, oneEndTime, realStartTime, realEndTime)); - Assert.assertTrue(DateUtil.isOverlap(oneStartTime2, oneEndTime2, realStartTime, realEndTime)); - Assert.assertFalse(DateUtil.isOverlap(oneStartTime3, oneEndTime3, realStartTime, realEndTime)); + public void parseTimeTest(){ + final DateTime dateTime = DateUtil.parse("12:23:34"); + Console.log(dateTime); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 57d6eb717..1d9df253e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -1,13 +1,16 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; public class LocalDateTimeUtilTest { @@ -217,4 +220,11 @@ public class LocalDateTimeUtilTest { final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); Assert.assertEquals(5, weekOfYear2); } + + @Test + public void ofTest2(){ + final Instant instant = DateUtil.parse("2022-02-22").toInstant(); + final LocalDateTime of = LocalDateTimeUtil.of((TemporalAccessor) instant); + Console.log(of); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index 35be0f241..5790f24fa 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -38,7 +38,6 @@ public class CheckedUtilTest { } } - @SuppressWarnings("ConstantConditions") @Test public void functionTest() { Func1 afunc = (funcParam) -> { diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 13bf3382f..d70490e8b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -452,4 +452,26 @@ public class FileUtilTest { List list = ListUtil.toList("a", "b", "c"); FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); } + + @Test + @Ignore + public void createTempFileTest(){ + File nullDirTempFile = FileUtil.createTempFile(); + Assert.assertTrue(nullDirTempFile.exists()); + + File suffixDirTempFile = FileUtil.createTempFile(".xlsx",true); + Assert.assertEquals("xlsx", FileUtil.getSuffix(suffixDirTempFile)); + + File prefixDirTempFile = FileUtil.createTempFile("prefix",".xlsx",true); + Assert.assertTrue(FileUtil.getPrefix(prefixDirTempFile).startsWith("prefix")); + } + + @Test + @Ignore + public void getTotalLinesTest() { + // 千万行秒级内返回 + final int totalLines = FileUtil.getTotalLines(FileUtil.file("")); + Assert.assertEquals(10000000, totalLines); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index 9feb53927..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -58,8 +59,8 @@ public class RangeTest { Assert.assertEquals(sb.toString(), "1#2#3#"); StringBuilder sb2 = new StringBuilder(); - DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); - Assert.assertEquals(sb2.toString(), ""); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); } @Test @@ -110,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -135,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java index 9338662a5..c50b3c11b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java @@ -137,4 +137,11 @@ public class UrlQueryTest { final UrlQuery query = UrlQuery.of(queryStr, null); Assert.assertEquals(queryStr, query.toString()); } + + @Test + public void parsePercentTest2(){ + String queryStr = "signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D"; + final UrlQuery query = UrlQuery.of(queryStr, null); + Assert.assertEquals(queryStr, query.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java new file mode 100755 index 000000000..3b1db3b67 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/text/AntPathMatcherTest.java @@ -0,0 +1,115 @@ +package cn.hutool.core.text; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class AntPathMatcherTest { + + @Test + public void matchesTest() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + boolean matched = antPathMatcher.match("/api/org/organization/{orgId}", "/api/org/organization/999"); + Assert.assertTrue(matched); + } + + @Test + public void matchesTest2() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + + String pattern = "/**/*.xml*"; + String path = "/WEB-INF/web.xml"; + boolean isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service"; + path = "org/codelabor/example/HelloWorldService"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + + pattern = "org/codelabor/*/**/*Service?"; + path = "org/codelabor/example/HelloWorldServices"; + isMatched = antPathMatcher.match(pattern, path); + Assert.assertTrue(isMatched); + } + + @Test + public void matchesTest3(){ + AntPathMatcher pathMatcher = new AntPathMatcher(); + pathMatcher.setCachePatterns(true); + pathMatcher.setCaseSensitive(true); + pathMatcher.setPathSeparator("/"); + pathMatcher.setTrimTokens(true); + + Assert.assertTrue(pathMatcher.match("a", "a")); + Assert.assertTrue(pathMatcher.match("a*", "ab")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/a")); + Assert.assertTrue(pathMatcher.match("a*/**/a", "ab/asdsa/asdasd/a")); + + Assert.assertTrue(pathMatcher.match("*", "a")); + Assert.assertTrue(pathMatcher.match("*/*", "a/a")); + } + + /** + * AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”,Linux下为“/”。靠谱写法如下两种方式: + * AntPathMatcher matcher = new AntPathMatcher(File.separator); + * AntPathMatcher matcher = new AntPathMatcher(System.getProperty("file.separator")); + */ + @Test + public void matchesTest4() { + AntPathMatcher pathMatcher = new AntPathMatcher(); + + // 精确匹配 + Assert.assertTrue(pathMatcher.match("/test", "/test")); + Assert.assertFalse(pathMatcher.match("test", "/test")); + + //测试通配符? + Assert.assertTrue(pathMatcher.match("t?st", "test")); + Assert.assertTrue(pathMatcher.match("te??", "test")); + Assert.assertFalse(pathMatcher.match("tes?", "tes")); + Assert.assertFalse(pathMatcher.match("tes?", "testt")); + + //测试通配符* + Assert.assertTrue(pathMatcher.match("*", "test")); + Assert.assertTrue(pathMatcher.match("test*", "test")); + Assert.assertTrue(pathMatcher.match("test/*", "test/Test")); + Assert.assertTrue(pathMatcher.match("*.*", "test.")); + Assert.assertTrue(pathMatcher.match("*.*", "test.test.test")); + Assert.assertFalse(pathMatcher.match("test*", "test/")); //注意这里是false 因为路径不能用*匹配 + Assert.assertFalse(pathMatcher.match("test*", "test/t")); //这同理 + Assert.assertFalse(pathMatcher.match("test*aaa", "testblaaab")); //这个是false 因为最后一个b无法匹配了 前面都是能匹配成功的 + + //测试通配符** 匹配多级URL + Assert.assertTrue(pathMatcher.match("/*/**", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/**/*", "/testing/testing")); + Assert.assertTrue(pathMatcher.match("/bla/**/bla", "/bla/testing/testing/bla/bla")); //这里也是true哦 + Assert.assertFalse(pathMatcher.match("/bla*bla/test", "/blaXXXbl/test")); + + Assert.assertFalse(pathMatcher.match("/????", "/bala/bla")); + Assert.assertFalse(pathMatcher.match("/**/*bla", "/bla/bla/bla/bbb")); + + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing/")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/*", "/XXXblaXXXX/testing/testing/bla/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing")); + Assert.assertTrue(pathMatcher.match("/*bla*/**/bla/**", "/XXXblaXXXX/testing/testing/bla/testing/testing.jpg")); + Assert.assertTrue(pathMatcher.match("/foo/bar/**", "/foo/bar")); + + //这个需要特别注意:{}里面的相当于Spring MVC里接受一个参数一样,所以任何东西都会匹配的 + Assert.assertTrue(pathMatcher.match("/{bla}.*", "/testing.html")); + Assert.assertFalse(pathMatcher.match("/{bla}.htm", "/testing.html")); //这样就是false了 + } + + /** + * 测试 URI 模板变量提取 + */ + @Test + public void testExtractUriTemplateVariables() { + AntPathMatcher antPathMatcher = new AntPathMatcher(); + HashMap map = (HashMap) antPathMatcher.extractUriTemplateVariables("/api/org/organization/{orgId}", + "/api/org" + + "/organization" + + "/999"); + Assert.assertEquals(1, map.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java index 2ce2eee8c..f9a2d4c30 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java @@ -78,4 +78,25 @@ public class StrJoinerTest { .append("3"); Assert.assertEquals("[1],[2],[3]", append.toString()); } + + @Test + public void lengthTest(){ + StrJoiner joiner = StrJoiner.of(",", "[", "]"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + + joiner.append("123"); + Assert.assertEquals(joiner.toString().length(), joiner.length()); + } + + @Test + public void mergeTest(){ + StrJoiner joiner1 = StrJoiner.of(",", "[", "]"); + joiner1.append("123"); + StrJoiner joiner2 = StrJoiner.of(",", "[", "]"); + joiner1.append("456"); + joiner1.append("789"); + + final StrJoiner merge = joiner1.merge(joiner2); + Assert.assertEquals("[123,456,789]", merge.toString()); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 5a0d71ca8..3ba18becf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -459,4 +459,11 @@ public class ArrayUtilTest { Assert.assertEquals(3, arrayAfterSplit.length); Assert.assertEquals(24, arrayAfterSplit[2].length); } + + @Test + public void getTest(){ + String[] a = {"a", "b", "c"}; + final Object o = ArrayUtil.get(a, -1); + Assert.assertEquals("c", o); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java index a2f141e39..b3c081546 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CoordinateUtilTest.java @@ -11,37 +11,46 @@ import org.junit.Test; */ public class CoordinateUtilTest { + @Test + public void wgs84ToGcj02Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); + Assert.assertEquals(116.41033392216508D, coordinate.getLng(), 15); + Assert.assertEquals(39.91640428150164D, coordinate.getLat(), 15); + } + + @Test + public void gcj02ToWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToWgs84(116.404, 39.915); + Assert.assertEquals(116.39766607783491D, coordinate.getLng(), 15); + Assert.assertEquals(39.91359571849836D, coordinate.getLat(), 15); + } + + @Test + public void wgs84toBd09Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.wgs84ToBd09(116.404, 39.915); + Assert.assertEquals(116.41671695444782D, coordinate.getLng(), 15); + Assert.assertEquals(39.922698713521726D, coordinate.getLat(), 15); + } + + @Test + public void bd09toWgs84Test(){ + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09toWgs84(116.404, 39.915); + Assert.assertEquals(116.39129143419822D, coordinate.getLng(), 15); + Assert.assertEquals(39.907253214522164D, coordinate.getLat(), 15); + } + @Test public void gcj02ToBd09Test() { - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.gcj02ToBd09(116.404, 39.915); - Assert.assertEquals(116.41036949371029D, gcj02.getLng(), 15); - Assert.assertEquals(39.92133699351021D, gcj02.getLat(), 15); + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.gcj02ToBd09(116.404, 39.915); + Assert.assertEquals(116.41036949371029D, coordinate.getLng(), 15); + Assert.assertEquals(39.92133699351022D, coordinate.getLat(), 15); } @Test public void bd09toGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.bd09ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39762729119315D, gcj02.getLng(), 15); - Assert.assertEquals(39.90865673957631D, gcj02.getLat(), 15); - } - - @Test - public void gcj02ToWgs84(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.39775550083061D, gcj02.getLng(), 15); - Assert.assertEquals(39.91359571849836D, gcj02.getLat(), 15); - } - - @Test - public void wgs84ToGcj02Test(){ - final CoordinateUtil.Coordinate gcj02 = CoordinateUtil.wgs84ToGcj02(116.404, 39.915); - Assert.assertEquals(116.41024449916938D, gcj02.getLng(), 15); - Assert.assertEquals(39.91640428150164D, gcj02.getLat(), 15); - } - - @Test - public void wgs84toBd09(){ - + final CoordinateUtil.Coordinate coordinate = CoordinateUtil.bd09ToGcj02(116.404, 39.915); + Assert.assertEquals(116.39762729119315D, coordinate.getLng(), 15); + Assert.assertEquals(39.90865673957631D, coordinate.getLat(), 15); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java index 0d5dcd378..9263feb66 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -17,9 +17,17 @@ public class CreditCodeUtilTest { Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); } + @Test + public void isCreditCode2() { + // 由于早期部分试点地区推行 法人和其他组织统一社会信用代码 较早,会存在部分代码不符合国家标准的情况。 + // 见:https://github.com/bluesky335/IDCheck + String testCreditCode = "91350211M00013FA1N"; + Assert.assertFalse(CreditCodeUtil.isCreditCode(testCreditCode)); + } + @Test public void randomCreditCode() { final String s = CreditCodeUtil.randomCreditCode(); Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); } -} \ No newline at end of file +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 70f6dd6e2..9a90c8fe3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -1,8 +1,13 @@ package cn.hutool.core.util; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; +import lombok.Data; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.lang.reflect.Field; @@ -91,33 +96,77 @@ public class ReflectUtilTest { @Test public void noneStaticInnerClassTest() { - final TestAClass testAClass = ReflectUtil.newInstanceIfPossible(TestAClass.class); + final NoneStaticClass testAClass = ReflectUtil.newInstanceIfPossible(NoneStaticClass.class); Assert.assertNotNull(testAClass); Assert.assertEquals(2, testAClass.getA()); } + @Data static class TestClass { private int a; - - public int getA() { - return a; - } - - public void setA(int a) { - this.a = a; - } } + @Data @SuppressWarnings("InnerClassMayBeStatic") - class TestAClass { + class NoneStaticClass { private int a = 2; + } - public int getA() { - return a; + @Test + @Ignore + public void getMethodBenchTest(){ + // 预热 + getMethod(TestBenchClass.class, false, "getH"); + + final TimeInterval timer = DateUtil.timer(); + timer.start(); + for (int i = 0; i < 100000000; i++) { + ReflectUtil.getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + + timer.restart(); + for (int i = 0; i < 100000000; i++) { + getMethod(TestBenchClass.class, false, "getH"); + } + Console.log(timer.interval()); + } + + @Data + static class TestBenchClass { + private int a; + private String b; + private String c; + private String d; + private String e; + private String f; + private String g; + private String h; + private String i; + private String j; + private String k; + private String l; + private String m; + private String n; + } + + public static Method getMethod(Class> clazz, boolean ignoreCase, String methodName, Class>... paramTypes) throws SecurityException { + if (null == clazz || StrUtil.isBlank(methodName)) { + return null; } - public void setA(int a) { - this.a = a; + Method res = null; + final Method[] methods = ReflectUtil.getMethods(clazz); + if (ArrayUtil.isNotEmpty(methods)) { + for (Method method : methods) { + if (StrUtil.equals(methodName, method.getName(), ignoreCase) + && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) + && (res == null + || res.getReturnType().isAssignableFrom(method.getReturnType()))) { + res = method; + } + } } + return res; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java index 309baddf7..afbc21754 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java @@ -186,4 +186,13 @@ public class ZipUtilTest { ZipUtil.zip(out, new String[]{"sm1_alias.txt"}, new InputStream[]{FileUtil.getInputStream("d:/test/sm4_1.txt")}); } + + @Test + @Ignore + public void zipMultiFileTest(){ + File[] dd={FileUtil.file("d:\\test\\qr_a.jpg") + ,FileUtil.file("d:\\test\\qr_b.jpg")}; + + ZipUtil.zip(FileUtil.file("d:\\test\\qr.zip"),false,dd); + } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 7f6b31ad6..7e88dc48d 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-cron diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java index 865838c25..d92a436df 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/DayOfMonthValueMatcher.java @@ -12,8 +12,6 @@ import java.util.List; */ public class DayOfMonthValueMatcher extends BoolArrayValueMatcher { - private static final int[] LAST_DAYS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - /** * 构造 * diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2249e5391..15a2bdca3 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index 90ef70540..518f09917 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,5 +1,6 @@ package cn.hutool.crypto; +import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -22,6 +23,7 @@ import java.io.InputStream; import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -988,4 +990,14 @@ public class KeyUtil { throw new CryptoException(e); } } + + /** + * 将密钥编码为Base64格式 + * @param key 密钥 + * @return Base64格式密钥 + * @since 5.7.22 + */ + public static String toBase64(Key key){ + return Base64.encode(key.getEncoded()); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 553b9dfdb..d84f99d5a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -20,6 +20,7 @@ import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; import org.bouncycastle.crypto.signers.StandardDSAEncoding; import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.encoders.Hex; import java.math.BigInteger; import java.security.PrivateKey; @@ -521,7 +522,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.5.9 */ public byte[] getD() { - return BigIntegers.asUnsignedByteArray(getDBigInteger()); + return BigIntegers.asUnsignedByteArray(32,getDBigInteger()); } /** @@ -531,7 +532,7 @@ public class SM2 extends AbstractAsymmetricCrypto { * @since 5.7.17 */ public String getDHex() { - return String.format("%064x", new BigInteger(1, getD())); + return new String(Hex.encode(getD())); } /** diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 2cd71e794..576a85cf8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -58,4 +58,11 @@ public class KeyUtilTest { final PrivateKey privateKey = KeyUtil.generatePrivateKey("DH", privateKeyBytes); Assert.assertEquals(dh.getPrivate(), privateKey); } + + @Test + public void generateSm4KeyTest(){ + // https://github.com/dromara/hutool/issues/2150 + Assert.assertEquals(16, KeyUtil.generateKey("sm4").getEncoded().length); + Assert.assertEquals(32, KeyUtil.generateKey("sm4", 256).getEncoded().length); + } } diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4785e323b..fdffcbe3a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-db @@ -103,6 +103,14 @@ ${mongo.version} true + redis.clients @@ -149,19 +157,19 @@ org.postgresql postgresql - 42.3.2 + 42.3.3 test com.microsoft.sqlserver mssql-jdbc - 9.4.1.jre8 + 10.2.0.jre8 test org.slf4j slf4j-simple - 1.7.32 + 1.7.36 test diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index 28f1bfc2b..886984e1f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -73,7 +73,7 @@ public class SqlConnRunner extends DialectRunner { /** * 构造 * - * @param driverClassName 驱动类名,,用于识别方言 + * @param driverClassName 驱动类名,用于识别方言 */ public SqlConnRunner(String driverClassName) { super(driverClassName); diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 04854a10e..caea07f65 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -185,15 +185,38 @@ public class MetaUtil { * @return Table对象 */ public static Table getTableMeta(DataSource ds, String tableName) { + return getTableMeta(ds, null, null, tableName); + } + + /** + * 获得表的元信息 + * 注意如果需要获取注释,某些数据库如MySQL,需要在配置中添加: + * + * remarks = true + * useInformationSchema = true + * + * + * @param ds 数据源 + * @param tableName 表名 + * @param catalog catalog name,{@code null}表示自动获取,见:{@link #getCataLog(Connection)} + * @param schema a schema name pattern,{@code null}表示自动获取,见:{@link #getSchema(Connection)} + * @return Table对象 + * @since 5.7.22 + */ + public static Table getTableMeta(DataSource ds, String catalog, String schema, String tableName) { final Table table = Table.create(tableName); Connection conn = null; try { conn = ds.getConnection(); // catalog和schema获取失败默认使用null代替 - final String catalog = getCataLog(conn); + if(null == catalog){ + catalog = getCataLog(conn); + } table.setCatalog(catalog); - final String schema = getSchema(conn); + if(null == schema){ + schema = getSchema(conn); + } table.setSchema(schema); final DatabaseMetaData metaData = conn.getMetaData(); diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 3e05431b2..a5a11cadd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f4de10b6..2b7542c34 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-extra @@ -19,7 +19,7 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java index 89d0b5249..de805450d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/ExpressionFactory.java @@ -9,7 +9,7 @@ import cn.hutool.extra.expression.ExpressionEngine; import cn.hutool.extra.expression.ExpressionException; /** - * 表达式语言引擎工厂类,,用于根据用户引入的表达式jar,自动创建对应的引擎对象 + * 表达式语言引擎工厂类,用于根据用户引入的表达式jar,自动创建对应的引擎对象 * * @since 5.5.0 * @author looly diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index f5b336527..607dd2c14 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -134,6 +134,17 @@ public class Ftp extends AbstractFtp { this.init(); } + /** + * 构造 + * + * @param client 自定义实例化好的{@link FTPClient} + * @since 5.7.22 + */ + public Ftp(FTPClient client) { + super(FtpConfig.create()); + this.client = client; + } + /** * 初始化连接 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java index a681eea14..cbe3313a7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/GanymedUtil.java @@ -79,7 +79,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -91,7 +91,7 @@ public class GanymedUtil { /** * 执行Shell命令 * - * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 * * * @param session Session会话 @@ -109,7 +109,7 @@ public class GanymedUtil { result = IoUtil.read(new StreamGobbler(session.getStdout()), charset); if(null != errStream){ // 错误输出 - IoUtil.copy(new StreamGobbler(session.getStdout()), errStream); + IoUtil.copy(new StreamGobbler(session.getStderr()), errStream); } } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0cff463c3..284c72dfb 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index b39c6e8ad..8b3ebe441 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -24,6 +24,11 @@ import java.util.Map.Entry; @SuppressWarnings("unchecked") public abstract class HttpBase { + /** + * 默认的请求编码、URL的encode、decode编码 + */ + protected static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + /** * HTTP/1.0 */ @@ -40,7 +45,7 @@ public abstract class HttpBase { /** * 编码 */ - protected Charset charset = CharsetUtil.CHARSET_UTF_8; + protected Charset charset = DEFAULT_CHARSET; /** * http版本 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 99f28b1e1..2015bdc77 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -33,6 +33,7 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; + private static boolean decodeUrl = false; /** * 获取全局默认的超时时长 @@ -124,12 +125,36 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @return 是否忽略解码URL + * @since 5.7.22 + */ + public static boolean isDecodeUrl() { + return decodeUrl; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param customDecodeUrl 是否忽略解码URL + * @since 5.7.22 + */ + synchronized public static void setDecodeUrl(boolean customDecodeUrl) { + decodeUrl = customDecodeUrl; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * * @return {@link CookieManager} - * @since 4.1.0 * @see GlobalCookieManager#getCookieManager() + * @since 4.1.0 */ public static CookieManager getCookieManager() { return GlobalCookieManager.getCookieManager(); @@ -139,8 +164,8 @@ public class HttpGlobalConfig implements Serializable { * 自定义{@link CookieManager} * * @param customCookieManager 自定义的{@link CookieManager} - * @since 4.5.14 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.5.14 */ synchronized public static void setCookieManager(CookieManager customCookieManager) { GlobalCookieManager.setCookieManager(customCookieManager); @@ -149,8 +174,8 @@ public class HttpGlobalConfig implements Serializable { /** * 关闭Cookie * - * @since 4.1.9 * @see GlobalCookieManager#setCookieManager(CookieManager) + * @since 4.1.9 */ synchronized public static void closeCookie() { GlobalCookieManager.setCookieManager(null); @@ -164,7 +189,7 @@ public class HttpGlobalConfig implements Serializable { * @since 5.7.4 */ synchronized public static void allowPatch() { - if(isAllowPatch){ + if (isAllowPatch) { return; } final Field methodsField = ReflectUtil.getField(HttpURLConnection.class, "methods"); @@ -181,7 +206,7 @@ public class HttpGlobalConfig implements Serializable { // 检查注入是否成功 final Object staticFieldValue = ReflectUtil.getStaticFieldValue(methodsField); - if(false == ArrayUtil.equals(methods, staticFieldValue)){ + if (false == ArrayUtil.equals(methods, staticFieldValue)) { throw new HttpException("Inject value to field [methods] failed!"); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7c7a68e35..864363e19 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,7 +12,6 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.body.BytesBody; @@ -129,18 +128,24 @@ public class HttpRequest extends HttpBase { } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)} + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接,默认自动编码URL中的参数等信息 * @return HttpRequest * @since 5.7.18 */ public static HttpRequest of(String url) { - return of(url, CharsetUtil.CHARSET_UTF_8); + return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); } /** - * 构建一个HTTP请求 + * 构建一个HTTP请求 + * 对于传入的URL,可以自定义是否解码已经编码的内容。 + * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数, + * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param url URL链接 * @param charset 编码,如果为{@code null}不自动解码编码URL @@ -266,7 +271,9 @@ public class HttpRequest extends HttpBase { * 构造,URL编码默认使用UTF-8 * * @param url URL + * @deprecated 请使用 {@link #of(String)} */ + @Deprecated public HttpRequest(String url) { this(UrlBuilder.ofHttp(url)); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index a6904f6f4..2ed832948 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -75,7 +75,7 @@ public class HttpUtil { * @since 3.0.9 */ public static HttpRequest createRequest(Method method, String url) { - return new HttpRequest(url).method(method); + return HttpRequest.of(url).method(method); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java index bc94c8a1e..a52462e4a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/SSLSocketFactoryBuilder.java @@ -90,6 +90,6 @@ public class SSLSocketFactoryBuilder implements SSLProtocols { * @throws KeyManagementException Key管理异常 */ public SSLSocketFactory build() throws NoSuchAlgorithmException, KeyManagementException { - return this.sslContextBuilder.build().getSocketFactory(); + return this.sslContextBuilder.buildChecked().getSocketFactory(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java index 00ad46b87..18dbb9682 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpUtilTest.java @@ -352,4 +352,14 @@ public class HttpUtilTest { .execute().body(); Console.log(body); } + + @Test + @Ignore + public void getPicTest(){ + HttpGlobalConfig.setDecodeUrl(false); + String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; + + final HttpRequest request = HttpRequest.of(url).method(Method.GET); + Console.log(request.execute().body()); + } } diff --git a/hutool-http/src/test/java/cn/hutool/http/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/RestTest.java index 9371c6039..a4c9f92da 100644 --- a/hutool-http/src/test/java/cn/hutool/http/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/RestTest.java @@ -63,6 +63,7 @@ public class RestTest { .body(JSONUtil.createObj() .set("advertiser_ids", new Long[] {1690657248243790L}) .set("fields", new String[] {"id", "name", "status"}).toString()); + Console.log(request); Console.log(request.execute().body()); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 03cbd5837..f3f427fdf 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组 * JSON数组是表示中括号括住的数据表现形式 @@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则 + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java index 202406cf8..00e2aa507 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java @@ -66,7 +66,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null + * @return JSONArray对象,如果值为{@code null},返回{@code null},非JSONArray类型,尝试转换,转换失败抛出异常 */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); @@ -85,7 +85,7 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter { * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 * * @param key KEY - * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null + * @return JSONObject对象,如果值为{@code null},返回{@code null},非JSONObject类型,尝试转换,转换失败抛出异常 */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应 * Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..ca42e36a4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象 @@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if (null != keyComparator) { + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + } else { + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); @@ -699,6 +719,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); + return; } init(new JSONTokener(StrUtil.trim(source), this.config)); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 6fb43e6db..7afa29552 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -229,7 +229,7 @@ public class JSONUtil { json = (JSON) obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); - json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); + json = isTypeJSONArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config); } else if (obj instanceof MapWrapper) { // MapWrapper实现了Iterable会被当作JSONArray,此处做修正 json = parseObj(obj, config); @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // @@ -821,9 +821,22 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSON(String)} */ + @Deprecated public static boolean isJson(String str) { - return isJsonObj(str) || isJsonArray(str); + return isTypeJSON(str); + } + + /** + * 是否为JSON类型字符串,首尾都为大括号或中括号判定为JSON字符串 + * + * @param str 字符串 + * @return 是否为JSON类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSON(String str) { + return isTypeJSONObject(str) || isTypeJSONArray(str); } /** @@ -832,8 +845,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONObject(String)} */ + @Deprecated public static boolean isJsonObj(String str) { + return isTypeJSONObject(str); + } + + /** + * 是否为JSONObject类型字符串,首尾都为大括号判定为JSONObject字符串 + * + * @param str 字符串 + * @return 是否为JSON字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONObject(String str) { if (StrUtil.isBlank(str)) { return false; } @@ -846,8 +872,21 @@ public class JSONUtil { * @param str 字符串 * @return 是否为JSON字符串 * @since 3.3.0 + * @deprecated 方法名称有歧义,请使用 {@link #isTypeJSONArray(String)} */ + @Deprecated public static boolean isJsonArray(String str) { + return isTypeJSONArray(str); + } + + /** + * 是否为JSONArray类型的字符串,首尾都为中括号判定为JSONArray字符串 + * + * @param str 字符串 + * @return 是否为JSONArray类型字符串 + * @since 5.7.22 + */ + public static boolean isTypeJSONArray(String str) { if (StrUtil.isBlank(str)) { return false; } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 01bcad54b..580bf7ed0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,36 +1,56 @@ package cn.hutool.json.serialize; +import cn.hutool.json.JSON; + import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.json.JSON; - /** * 全局的序列化和反序列化器映射 * 在JSON和Java对象转换过程中,优先使用注册于此处的自定义转换 - * + * * @author Looly * */ public class GlobalSerializeMapping { - + private static Map> serializerMap; private static Map> deserializerMap; - + + static { + serializerMap = new ConcurrentHashMap<>(); + deserializerMap = new ConcurrentHashMap<>(); + + final TemporalAccessorSerializer localDateSerializer = new TemporalAccessorSerializer(LocalDate.class); + serializerMap.put(LocalDate.class, localDateSerializer); + deserializerMap.put(LocalDate.class, localDateSerializer); + + final TemporalAccessorSerializer localDateTimeSerializer = new TemporalAccessorSerializer(LocalDateTime.class); + serializerMap.put(LocalDateTime.class, localDateTimeSerializer); + deserializerMap.put(LocalDateTime.class, localDateTimeSerializer); + + final TemporalAccessorSerializer localTimeSerializer = new TemporalAccessorSerializer(LocalTime.class); + serializerMap.put(LocalTime.class, localTimeSerializer); + deserializerMap.put(LocalTime.class, localTimeSerializer); + } + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ public static void put(Type type, JSONArraySerializer> serializer) { putInternal(type, serializer); } - + /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -40,7 +60,7 @@ public class GlobalSerializeMapping { /** * 加入自定义的序列化器 - * + * * @param type 对象类型 * @param serializer 序列化器实现 */ @@ -50,10 +70,10 @@ public class GlobalSerializeMapping { } serializerMap.put(type, serializer); } - + /** * 加入自定义的反序列化器 - * + * * @param type 对象类型 * @param deserializer 反序列化器实现 */ @@ -63,7 +83,7 @@ public class GlobalSerializeMapping { } deserializerMap.put(type, deserializer); } - + /** * 获取自定义的序列化器,如果未定义返回{@code null} * @param type 类型 @@ -75,7 +95,7 @@ public class GlobalSerializeMapping { } return serializerMap.get(type); } - + /** * 获取自定义的反序列化器,如果未定义返回{@code null} * @param type 类型 diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java new file mode 100755 index 000000000..7ca54d49b --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/TemporalAccessorSerializer.java @@ -0,0 +1,75 @@ +package cn.hutool.json.serialize; + +import cn.hutool.json.JSON; +import cn.hutool.json.JSONException; +import cn.hutool.json.JSONObject; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAccessor; + +/** + * {@link TemporalAccessor}的JSON自定义序列化实现 + * + * @author looly + * @since 5.7.22 + */ +public class TemporalAccessorSerializer implements JSONObjectSerializer, JSONDeserializer { + + private static final String YEAR_KEY = "year"; + private static final String MONTH_KEY = "month"; + private static final String DAY_KEY = "day"; + private static final String HOUR_KEY = "hour"; + private static final String MINUTE_KEY = "minute"; + private static final String SECOND_KEY = "second"; + private static final String NANO_KEY = "nano"; + + private final Class extends TemporalAccessor> temporalAccessorClass; + + public TemporalAccessorSerializer(Class extends TemporalAccessor> temporalAccessorClass) { + this.temporalAccessorClass = temporalAccessorClass; + } + + @Override + public void serialize(JSONObject json, TemporalAccessor bean) { + if (bean instanceof LocalDate) { + final LocalDate localDate = (LocalDate) bean; + json.set(YEAR_KEY, localDate.getYear()); + json.set(MONTH_KEY, localDate.getMonthValue()); + json.set(DAY_KEY, localDate.getDayOfMonth()); + } else if (bean instanceof LocalDateTime) { + final LocalDateTime localDateTime = (LocalDateTime) bean; + json.set(YEAR_KEY, localDateTime.getYear()); + json.set(MONTH_KEY, localDateTime.getMonthValue()); + json.set(DAY_KEY, localDateTime.getDayOfMonth()); + json.set(HOUR_KEY, localDateTime.getHour()); + json.set(MINUTE_KEY, localDateTime.getMinute()); + json.set(SECOND_KEY, localDateTime.getSecond()); + json.set(NANO_KEY, localDateTime.getNano()); + } else if (bean instanceof LocalTime) { + final LocalTime localTime = (LocalTime) bean; + json.set(HOUR_KEY, localTime.getHour()); + json.set(MINUTE_KEY, localTime.getMinute()); + json.set(SECOND_KEY, localTime.getSecond()); + json.set(NANO_KEY, localTime.getNano()); + } else { + throw new JSONException("Unsupported type to JSON: {}", bean.getClass().getName()); + } + } + + @Override + public TemporalAccessor deserialize(JSON json) { + final JSONObject jsonObject = (JSONObject) json; + if (LocalDate.class.equals(this.temporalAccessorClass)) { + return LocalDate.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY)); + } else if (LocalDateTime.class.equals(this.temporalAccessorClass)) { + return LocalDateTime.of(jsonObject.getInt(YEAR_KEY), jsonObject.getInt(MONTH_KEY), jsonObject.getInt(DAY_KEY), + jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } else if (LocalTime.class.equals(this.temporalAccessorClass)) { + return LocalTime.of(jsonObject.getInt(HOUR_KEY), jsonObject.getInt(MINUTE_KEY), jsonObject.getInt(SECOND_KEY), jsonObject.getInt(NANO_KEY)); + } + + throw new JSONException("Unsupported type from JSON: {}", this.temporalAccessorClass); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java index f1f59b68e..de67cb0e8 100755 --- a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -5,6 +5,8 @@ import org.junit.Assert; import org.junit.Test; import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.Month; /** @@ -29,6 +31,30 @@ public class Issue2090Test { Assert.assertNotNull(jsonObject.toString()); } + @Test + public void toBeanLocalDateTest(){ + LocalDate d = LocalDate.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDate d2 = obj.toBean(LocalDate.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalDateTimeTest(){ + LocalDateTime d = LocalDateTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalDateTime d2 = obj.toBean(LocalDateTime.class); + Assert.assertEquals(d, d2); + } + + @Test + public void toBeanLocalTimeTest(){ + LocalTime d = LocalTime.now(); + final JSONObject obj = JSONUtil.parseObj(d); + LocalTime d2 = obj.toBean(LocalTime.class); + Assert.assertEquals(d, d2); + } + @Test public void monthTest(){ final JSONObject jsonObject = new JSONObject(); diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131 + * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java new file mode 100755 index 000000000..6eb7ee7d3 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssuesI4V14NTest.java @@ -0,0 +1,20 @@ +package cn.hutool.json; + +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class IssuesI4V14NTest { + + @Test + public void parseTest(){ + String str = "{\"A\" : \"A\\nb\"}"; + final JSONObject jsonObject = JSONUtil.parseObj(str); + Assert.assertEquals("A\nb", jsonObject.getStr("A")); + + final Map map = jsonObject.toBean(new TypeReference>() {}); + Assert.assertEquals("A\nb", map.get("A")); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 328bca47f..7d0b17f4f 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 9d025a157..31be53fe3 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 6d05e81e3..16f3630ea 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java index ec0140e03..b65b7f20c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelFileUtil.java @@ -62,16 +62,16 @@ public class ExcelFileUtil { * 如果强转成PushbackInputStream在调用FileMagic.valueOf(inputStream)时会报错 * {@link FileMagic} * 报错内容:getFileMagic() only operates on streams which support mark(int) - * 此处修改成 final InputStream inputStream = FileMagic.prepareToCheckMagic(in) + * 此处修改成 final InputStream in = FileMagic.prepareToCheckMagic(in) * * @param in {@link InputStream} * @author kefan.qu */ private static FileMagic getFileMagic(InputStream in) { FileMagic magic; - final InputStream inputStream = FileMagic.prepareToCheckMagic(in); + in = FileMagic.prepareToCheckMagic(in); try { - magic = FileMagic.valueOf(inputStream); + magic = FileMagic.valueOf(in); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 2ff3d55e6..7f44720e7 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -283,7 +283,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param cellHandler 单元格处理器,用于处理读到的单元格及其数据 * @since 5.3.8 @@ -294,7 +294,7 @@ public class ExcelReader extends ExcelBase { /** * 读取工作簿中指定的Sheet,此方法为类流处理方式,当读到指定单元格时,会调用CellEditor接口 - * 用户通过实现此接口,可以更加灵活的处理每个单元格的数据。 + * 用户通过实现此接口,可以更加灵活地处理每个单元格的数据。 * * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 结束行(包含,从0开始计数) @@ -364,7 +364,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 * @return Map的列表 @@ -378,7 +378,7 @@ public class ExcelReader extends ExcelBase { * 读取Excel为Bean的列表 * * @param Bean类型 - * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,,从0开始计数 + * @param headerRowIndex 标题所在行,如果标题行在读取的内容行中间,这行做为数据将忽略,从0开始计数 * @param startRowIndex 起始行(包含,从0开始计数) * @param endRowIndex 读取结束行(包含,从0开始计数) * @param beanType 每行对应Bean的类型 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 6d94b4815..24256a862 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -33,23 +33,23 @@ public class StyleSet implements Serializable { /** * 标题样式 */ - protected CellStyle headCellStyle; + protected final CellStyle headCellStyle; /** * 默认样式 */ - protected CellStyle cellStyle; + protected final CellStyle cellStyle; /** * 默认数字样式 */ - protected CellStyle cellStyleForNumber; + protected final CellStyle cellStyleForNumber; /** * 默认日期样式 */ - protected CellStyle cellStyleForDate; + protected final CellStyle cellStyleForDate; /** * 默认链接样式 */ - protected CellStyle cellStyleForHyperlink; + protected final CellStyle cellStyleForHyperlink; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java index db3104681..132c3809a 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/FormulaCellValue.java @@ -18,11 +18,11 @@ public class FormulaCellValue implements CellValue, CellSetter { /** * 公式 */ - String formula; + private final String formula; /** * 结果,使用ExcelWriter时可以不用 */ - Object result; + private final Object result; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java index 6bb0a64f4..c2e693091 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/setters/HyperlinkCellSetter.java @@ -1,6 +1,5 @@ package cn.hutool.poi.excel.cell.setters; -import cn.hutool.core.lang.Console; import cn.hutool.poi.excel.cell.CellSetter; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Hyperlink; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java index 25f830d03..99f4a532f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/editors/NumericToIntEditor.java @@ -6,15 +6,15 @@ import cn.hutool.poi.excel.cell.CellEditor; /** * POI中NUMRIC类型的值默认返回的是Double类型,此编辑器用于转换其为int型 - * @author Looly * + * @author Looly */ -public class NumericToIntEditor implements CellEditor{ +public class NumericToIntEditor implements CellEditor { @Override public Object edit(Cell cell, Object value) { - if(value instanceof Number) { - return ((Number)value).intValue(); + if (value instanceof Number) { + return ((Number) value).intValue(); } return value; } 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 ee5c5710a..b50034cb3 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 @@ -128,7 +128,7 @@ public class Excel07SaxReader implements ExcelSaxReader { // 获取共享字符串表 try { - this.handler.sharedStringsTable = xssfReader.getSharedStringsTable(); + this.handler.sharedStrings = xssfReader.getSharedStringsTable(); } catch (IOException e) { throw new IORuntimeException(e); } catch (InvalidFormatException e) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java index 4c66c348a..f6fdcc217 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java @@ -13,7 +13,7 @@ import org.apache.poi.hssf.eventusermodel.FormatTrackingHSSFListener; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.ooxml.util.SAXHelper; import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; @@ -53,13 +53,13 @@ public class ExcelSaxUtil { /** * 根据数据类型获取数据 * - * @param cellDataType 数据类型枚举 - * @param value 数据值 - * @param sharedStringsTable {@link SharedStringsTable} - * @param numFmtString 数字格式名 + * @param cellDataType 数据类型枚举 + * @param value 数据值 + * @param sharedStrings {@link SharedStrings} + * @param numFmtString 数字格式名 * @return 数据值 */ - public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) { + public static Object getDataValue(CellDataType cellDataType, String value, SharedStrings sharedStrings, String numFmtString) { if (null == value) { return null; } @@ -85,7 +85,7 @@ public class ExcelSaxUtil { case SSTINDEX: try { final int index = Integer.parseInt(value); - result = sharedStringsTable.getItemAt(index).getString(); + result = sharedStrings.getItemAt(index).getString(); } catch (NumberFormatException e) { result = value; } @@ -208,8 +208,8 @@ public class ExcelSaxUtil { * @param formatIndex 格式索引,一般用于内建格式 * @param formatString 格式字符串 * @return 是否为日期格式 - * @since 5.5.3 * @see ExcelDateUtil#isDateFormat(int, String) + * @since 5.5.3 */ public static boolean isDateFormat(int formatIndex, String formatString) { return ExcelDateUtil.isDateFormat(formatIndex, formatString); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java index ea64bb888..5d61bde85 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.handler.RowHandler; import org.apache.poi.ss.usermodel.BuiltinFormats; -import org.apache.poi.xssf.model.SharedStringsTable; +import org.apache.poi.xssf.model.SharedStrings; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; import org.xml.sax.Attributes; @@ -28,7 +28,7 @@ public class SheetDataSaxHandler extends DefaultHandler { // 单元格的格式表,对应style.xml protected StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml - protected SharedStringsTable sharedStringsTable; + protected SharedStrings sharedStrings; // sheet的索引,从0开始 protected int sheetIndex; @@ -238,7 +238,7 @@ public class SheetDataSaxHandler extends DefaultHandler { fillBlankCell(preCoordinate, curCoordinate, false); final String contentStr = StrUtil.trim(lastContent); - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStrings, this.numFmtString); if (false == this.lastFormula.isEmpty()) { value = new FormulaCellValue(StrUtil.trim(lastFormula), value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java index a7c79e30d..4aca4d561 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/ofd/OfdWriter.java @@ -118,6 +118,7 @@ public class OfdWriter implements Serializable, Closeable { * @param div 节点,可以是段落、Canvas、Img或者填充 * @return this */ + @SuppressWarnings("rawtypes") public OfdWriter add(Div div) { this.doc.add(div); return this; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 3f438203b..5b63d2221 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -22,6 +22,7 @@ public enum PicType { /** * 构造 + * * @param value 图片类型值 */ PicType(int value) { diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f1d4bcc68..cef0fa850 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index b9cd5073f..03e7a51ed 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 11da40893..cdf4bd3ee 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ee10d1fef..414aafe31 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 906bd8b87..984035b17 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.21-SNAPSHOT + 5.7.22-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +
+ * remarks = true + * useInformationSchema = true + *
- * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,不会产生阻塞。 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭Session,可能产生阻塞。 *