diff --git a/CHANGELOG.md b/CHANGELOG.md index edb403142..fff71d756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,33 @@ ------------------------------------------------------------------------------------------------------------- +# 5.7.16 (2021-10-31) + +### 🐣新特性 +* 【core 】 增加DateTime.toLocalDateTime +* 【core 】 CharSequenceUtil增加normalize方法(pr#444@Gitee) +* 【core 】 MailAccount增加setEncodefilename()方法,可选是否编码附件的文件名(issue#I4F160@Gitee) +* 【core 】 MailAccount中charset增加null时的默认规则 +* 【core 】 NumberUtil.compare修正注释说明(issue#I4FAJ1@Gitee) +* 【core 】 增加RFC3986类 +* 【extra 】 Sftp增加put和upload重载(issue#I4FGDH@Gitee) +* 【core 】 TemporalUtil增加toChronoUnit、toTimeUnit方法(issue#I4FGDH@Gitee) +* 【core 】 StopWatch增加prettyPrint重载(issue#1910@Github) +* 【core 】 修改RegexPool中Ipv4正则 +* 【json 】 Filter改为MutablePair,以便编辑键值对(issue#1921@Github) +* 【core 】 Opt增加peeks方法(pr#445@Gitee) +* 【extra 】 MailAccount中user默认值改为邮箱全称(issue#I4FYVY@Gitee) + +### 🐞Bug修复 +* 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) +* 【core 】 修复StrBuilder中总长度计算问题(issue#I4F9L7@Gitee) +* 【core 】 修复CharSequenceUtil.wrapIfMissing预定义长度计算问题(issue#I4FDZ2@Gitee) +* 【poi 】 修复合并单元格为日期时,导出单元格数据为数字问题(issue#1911@Github) +* 【core 】 修复CompilerUtil.getFileManager参数没有使用的问题(issue#I4FIO6@Gitee) +* 【core 】 修复NetUtil.isInRange的cidr判断问题(pr#1917@Github) + +------------------------------------------------------------------------------------------------------------- + # 5.7.15 (2021-10-21) ### 🐣新特性 @@ -17,6 +44,8 @@ * 【core 】 ZipUtil增加append方法(pr#441@Gitee) * 【core 】 CollUtil增加重载(issue#I4E9FS@Gitee) * 【core 】 CopyOptions新增setFieldValueEditor(issue#I4E08T@Gitee) +* 【core 】 增加SystemPropsUtil(issue#1918@Gitee) +* 【core 】 增加`hutool.date.lenient`系统属性(issue#1918@Gitee) ### 🐞Bug修复 * 【core 】 修复CollUtil.isEqualList两个null返回错误问题(issue#1885@Github) diff --git a/README-EN.md b/README-EN.md index d11d3dfaa..f73d4c304 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.15 + 5.7.16 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.15' +implementation 'cn.hutool:hutool-all:5.7.16' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.15/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.16/) > 🔔️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 4bfc648e6..ae1aea6e8 100644 --- a/README.md +++ b/README.md @@ -142,20 +142,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.7.15 + 5.7.16 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.15' +implementation 'cn.hutool:hutool-all:5.7.16' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.15/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.16/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 7b9e0e8bb..e8406ce30 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.7.15 +5.7.16 diff --git a/docs/js/version.js b/docs/js/version.js index 36f2fde5c..3e6f04e99 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.7.15' \ No newline at end of file +var version = '5.7.16' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index f4cab970a..b97625af4 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index bc7be21a4..5590a0b53 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index b7ed8a410..96f02bf66 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 402341d6c..a484343e3 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 5bfe068ca..7e84b9cea 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index e1293b7d3..b22ac7ab2 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 5956c0bb2..ed9ac8a21 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java index 4cab13809..b0e35315f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java @@ -242,10 +242,11 @@ public class CopyOptions implements Serializable { } /** - * 转换字段名为编辑后的字段名 + * 编辑字段值 * - * @param fieldName 字段名 - * @return 编辑后的字段名 + * @param fieldName 字段名 + * @param fieldValue 字段值 + * @return 编辑后的字段值 * @since 5.7.15 */ protected Object editFieldValue(String fieldName, Object fieldValue) { diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java new file mode 100644 index 000000000..91faa74f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java @@ -0,0 +1,188 @@ +package cn.hutool.core.codec; + +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.BitSet; + +/** + * 百分号编码(Percent-encoding), 也称作URL编码(URL encoding)。
+ * 百分号编码可用于URI的编码,也可以用于"application/x-www-form-urlencoded"的MIME准备数据。 + * + *

+ * 百分号编码会对 URI 中不允许出现的字符或者其他特殊情况的允许的字符进行编码,对于被编码的字符,最终会转为以百分号"%“开头,后面跟着两位16进制数值的形式。 + * 举个例子,空格符(SP)是不允许的字符,在 ASCII 码对应的二进制值是"00100000”,最终转为"%20"。 + *

+ *

+ * 对于不同场景应遵循不同规范: + * + *

+ * + * @author looly + * @since 5.7.16 + */ +public class PercentCodec implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 从已知PercentCodec创建PercentCodec,会复制给定PercentCodec的安全字符 + * + * @param codec PercentCodec + * @return PercentCodec + */ + public static PercentCodec of(PercentCodec codec) { + return new PercentCodec((BitSet) codec.safeCharacters.clone()); + } + + /** + * 创建PercentCodec,使用指定字符串中的字符作为安全字符 + * + * @param chars 安全字符合集 + * @return PercentCodec + */ + public static PercentCodec of(CharSequence chars) { + final PercentCodec codec = new PercentCodec(); + final int length = chars.length(); + for (int i = 0; i < length; i++) { + codec.addSafe(chars.charAt(i)); + } + return codec; + } + + /** + * 存放安全编码 + */ + private final BitSet safeCharacters; + /** + * 是否编码空格为+ + */ + private boolean encodeSpaceAsPlus = false; + + /** + * 构造
+ * [a-zA-Z0-9]默认不被编码 + */ + public PercentCodec() { + this(new BitSet(256)); + } + + /** + * 构造 + * + * @param safeCharacters 安全字符,安全字符不被编码 + */ + public PercentCodec(BitSet safeCharacters) { + this.safeCharacters = safeCharacters; + } + + /** + * 增加安全字符
+ * 安全字符不被编码 + * + * @param c 字符 + * @return this + */ + public PercentCodec addSafe(char c) { + safeCharacters.set(c); + return this; + } + + /** + * 移除安全字符
+ * 安全字符不被编码 + * + * @param c 字符 + * @return this + */ + public PercentCodec removeSafe(char c) { + safeCharacters.clear(c); + return this; + } + + /** + * 增加安全字符到挡墙的PercentCodec + * + * @param codec PercentCodec + * @return this + */ + public PercentCodec or(PercentCodec codec) { + this.safeCharacters.or(codec.safeCharacters); + return this; + } + + /** + * 组合当前PercentCodec和指定PercentCodec为一个新的PercentCodec,安全字符为并集 + * + * @param codec PercentCodec + * @return 新的PercentCodec + */ + public PercentCodec orNew(PercentCodec codec) { + return of(this).or(codec); + } + + /** + * 是否将空格编码为+ + * + * @param encodeSpaceAsPlus 是否将空格编码为+ + * @return this + */ + public PercentCodec setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) { + this.encodeSpaceAsPlus = encodeSpaceAsPlus; + return this; + } + + /** + * 将URL中的字符串编码为%形式 + * + * @param path 需要编码的字符串 + * @param charset 编码, {@code null}返回原字符串,表示不编码 + * @return 编码后的字符串 + */ + public String encode(CharSequence path, Charset charset) { + if (null == charset || StrUtil.isEmpty(path)) { + return StrUtil.str(path); + } + + final StringBuilder rewrittenPath = new StringBuilder(path.length()); + final ByteArrayOutputStream buf = new ByteArrayOutputStream(); + final OutputStreamWriter writer = new OutputStreamWriter(buf, charset); + + int c; + for (int i = 0; i < path.length(); i++) { + c = path.charAt(i); + if (safeCharacters.get(c)) { + rewrittenPath.append((char) c); + } else if (encodeSpaceAsPlus && c == CharUtil.SPACE) { + // 对于空格单独处理 + rewrittenPath.append('+'); + } else { + // convert to external encoding before hex conversion + try { + writer.write((char) c); + writer.flush(); + } catch (IOException e) { + buf.reset(); + continue; + } + + byte[] ba = buf.toByteArray(); + for (byte toEncode : ba) { + // Converting each byte in the buffer + rewrittenPath.append('%'); + HexUtil.appendHex(rewrittenPath, toEncode, false); + } + buf.reset(); + } + } + return rewrittenPath.toString(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java index 1af24f204..fb5cbc635 100644 --- a/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java @@ -36,7 +36,7 @@ public class CompilerUtil { * @return {@link StandardJavaFileManager} */ public static StandardJavaFileManager getFileManager() { - return SYSTEM_COMPILER.getStandardFileManager(null, null, null); + return getFileManager(null); } /** @@ -47,7 +47,7 @@ public class CompilerUtil { * @since 5.5.8 */ public static StandardJavaFileManager getFileManager(DiagnosticListener diagnosticListener) { - return SYSTEM_COMPILER.getStandardFileManager(null, null, null); + return SYSTEM_COMPILER.getStandardFileManager(diagnosticListener, null, null); } /** 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 2475cb022..5cac6c295 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 @@ -20,6 +20,7 @@ import cn.hutool.core.convert.impl.EnumConverter; import cn.hutool.core.convert.impl.LocaleConverter; import cn.hutool.core.convert.impl.MapConverter; import cn.hutool.core.convert.impl.NumberConverter; +import cn.hutool.core.convert.impl.OptConverter; import cn.hutool.core.convert.impl.OptionalConverter; import cn.hutool.core.convert.impl.PathConverter; import cn.hutool.core.convert.impl.PeriodConverter; @@ -33,6 +34,7 @@ import cn.hutool.core.convert.impl.URIConverter; import cn.hutool.core.convert.impl.URLConverter; import cn.hutool.core.convert.impl.UUIDConverter; import cn.hutool.core.date.DateTime; +import cn.hutool.core.lang.Opt; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ObjectUtil; @@ -444,6 +446,7 @@ public class ConverterRegistry implements Serializable { defaultConverterMap.put(UUID.class, new UUIDConverter());// since 4.0.10 defaultConverterMap.put(StackTraceElement.class, new StackTraceElementConverter());// since 4.5.2 defaultConverterMap.put(Optional.class, new OptionalConverter());// since 5.0.0 + defaultConverterMap.put(Opt.class, new OptConverter());// since 5.7.16 return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/OptConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/OptConverter.java new file mode 100644 index 000000000..cf79c179e --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/OptConverter.java @@ -0,0 +1,21 @@ +package cn.hutool.core.convert.impl; + +import cn.hutool.core.convert.AbstractConverter; +import cn.hutool.core.lang.Opt; + +/** + * + * {@link Opt}对象转换器 + * + * @author Looly + * @since 5.7.16 + */ +public class OptConverter extends AbstractConverter> { + private static final long serialVersionUID = 1L; + + @Override + protected Opt convertInternal(Object value) { + return Opt.ofNullable(value); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java index 6758c3768..5727c460f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java @@ -7,11 +7,13 @@ import cn.hutool.core.date.format.GlobalCustomFormat; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.SystemPropsUtil; import java.sql.Timestamp; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Instant; +import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -287,7 +289,7 @@ public class DateTime extends Date { * @see DatePattern */ public DateTime(CharSequence dateStr, DateParser dateParser) { - this(dateStr, dateParser, true); + this(dateStr, dateParser, SystemPropsUtil.getBoolean(SystemPropsUtil.HUTOOL_DATE_LENIENT, true)); } /** @@ -701,6 +703,16 @@ public class DateTime extends Date { return new java.sql.Date(getTime()); } + /** + * 转换为 {@link LocalDateTime} + * + * @return {@link LocalDateTime} + * @since 5.7.16 + */ + public LocalDateTime toLocalDateTime() { + return LocalDateTimeUtil.of(this); + } + /** * 计算相差时长 * diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 4ba6d7a9b..37c935d04 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -2059,6 +2059,32 @@ public class DateUtil extends CalendarUtil { return format; } + /** + * 获取时长单位简写 + * + * @param unit 单位 + * @return 单位简写名称 + * @since 5.7.16 + */ + public static String getShotName(TimeUnit unit) { + switch (unit) { + case NANOSECONDS: + return "ns"; + case MICROSECONDS: + return "μs"; + case MILLISECONDS: + return "ms"; + case SECONDS: + return "s"; + case MINUTES: + return "min"; + case HOURS: + return "h"; + default: + return unit.name().toLowerCase(); + } + } + // ------------------------------------------------------------------------ Private method start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/date/StopWatch.java b/hutool-core/src/main/java/cn/hutool/core/date/StopWatch.java index 7bf6b3401..e83bd25de 100755 --- a/hutool-core/src/main/java/cn/hutool/core/date/StopWatch.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/StopWatch.java @@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil; import java.text.NumberFormat; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; /** * 秒表封装
@@ -48,7 +49,7 @@ public class StopWatch { * @return StopWatch * @since 5.5.2 */ - public static StopWatch create(String id){ + public static StopWatch create(String id) { return new StopWatch(id); } @@ -251,6 +252,17 @@ public class StopWatch { return this.lastTaskInfo; } + /** + * 获取所有任务的总花费时间 + * + * @param unit 时间单位,{@code null}表示默认{@link TimeUnit#NANOSECONDS} + * @return 花费时间 + * @since 5.7.16 + */ + public long getTotal(TimeUnit unit){ + return unit.convert(this.totalTimeNanos, TimeUnit.NANOSECONDS); + } + /** * 获取所有任务的总花费时间(纳秒) * @@ -270,7 +282,7 @@ public class StopWatch { * @see #getTotalTimeSeconds() */ public long getTotalTimeMillis() { - return DateUtil.nanosToMillis(this.totalTimeNanos); + return getTotal(TimeUnit.MILLISECONDS); } /** @@ -306,27 +318,62 @@ public class StopWatch { } /** - * 获取任务信息 + * 获取任务信息,类似于: + *
+	 *     StopWatch '[id]': running time = [total] ns
+	 * 
* * @return 任务信息 */ public String shortSummary() { - return StrUtil.format("StopWatch '{}': running time = {} ns", this.id, this.totalTimeNanos); + return shortSummary(null); + } + + /** + * 获取任务信息,类似于: + *
+	 *     StopWatch '[id]': running time = [total] [unit]
+	 * 
+ * + * @param unit 时间单位,{@code null}则默认为{@link TimeUnit#NANOSECONDS} + * @return 任务信息 + */ + public String shortSummary(TimeUnit unit) { + if(null == unit){ + unit = TimeUnit.NANOSECONDS; + } + return StrUtil.format("StopWatch '{}': running time = {} {}", + this.id, getTotal(unit), DateUtil.getShotName(unit)); + } + + /** + * 生成所有任务的一个任务花费时间表,单位纳秒 + * + * @return 任务时间表 + */ + public String prettyPrint() { + return prettyPrint(null); } /** * 生成所有任务的一个任务花费时间表 * + * @param unit 时间单位,{@code null}则默认{@link TimeUnit#NANOSECONDS} 纳秒 * @return 任务时间表 + * @since 5.7.16 */ - public String prettyPrint() { - StringBuilder sb = new StringBuilder(shortSummary()); + public String prettyPrint(TimeUnit unit) { + if (null == unit) { + unit = TimeUnit.NANOSECONDS; + } + + final StringBuilder sb = new StringBuilder(shortSummary(unit)); sb.append(FileUtil.getLineSeparator()); if (null == this.taskList) { sb.append("No task info kept"); } else { sb.append("---------------------------------------------").append(FileUtil.getLineSeparator()); - sb.append("ns % Task name").append(FileUtil.getLineSeparator()); + sb.append(DateUtil.getShotName(unit)).append(" % Task name").append(FileUtil.getLineSeparator()); sb.append("---------------------------------------------").append(FileUtil.getLineSeparator()); final NumberFormat nf = NumberFormat.getNumberInstance(); @@ -334,11 +381,12 @@ public class StopWatch { nf.setGroupingUsed(false); final NumberFormat pf = NumberFormat.getPercentInstance(); - pf.setMinimumIntegerDigits(3); + pf.setMinimumIntegerDigits(2); pf.setGroupingUsed(false); + for (TaskInfo task : getTaskInfo()) { - sb.append(nf.format(task.getTimeNanos())).append(" "); - sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" "); + sb.append(nf.format(task.getTime(unit))).append(" "); + sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" "); sb.append(task.getTaskName()).append(FileUtil.getLineSeparator()); } } @@ -370,6 +418,12 @@ public class StopWatch { private final String taskName; private final long timeNanos; + /** + * 构造 + * + * @param taskName 任务名称 + * @param timeNanos 花费时间(纳秒) + */ TaskInfo(String taskName, long timeNanos) { this.taskName = taskName; this.timeNanos = timeNanos; @@ -384,6 +438,17 @@ public class StopWatch { return this.taskName; } + /** + * 获取指定单位的任务花费时间 + * + * @param unit 单位 + * @return 任务花费时间 + * @since 5.7.16 + */ + public long getTime(TimeUnit unit) { + return unit.convert(this.timeNanos, TimeUnit.NANOSECONDS); + } + /** * 获取任务花费时间(单位:纳秒) * @@ -403,7 +468,7 @@ public class StopWatch { * @see #getTimeSeconds() */ public long getTimeMillis() { - return DateUtil.nanosToMillis(this.timeNanos); + return getTime(TimeUnit.MILLISECONDS); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TemporalUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/TemporalUtil.java index 28878b69a..d003f4971 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/TemporalUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/TemporalUtil.java @@ -3,6 +3,7 @@ package cn.hutool.core.date; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; +import java.util.concurrent.TimeUnit; /** * {@link Temporal} 工具类封装 @@ -38,4 +39,67 @@ public class TemporalUtil { public static long between(Temporal startTimeInclude, Temporal endTimeExclude, ChronoUnit unit) { return unit.between(startTimeInclude, endTimeExclude); } + + /** + * 将 {@link TimeUnit} 转换为 {@link ChronoUnit}. + * + * @param unit 被转换的{@link TimeUnit}单位,如果为{@code null}返回{@code null} + * @return {@link ChronoUnit} + * @since 5.7.16 + */ + public static ChronoUnit toChronoUnit(TimeUnit unit) throws IllegalArgumentException { + if (null == unit) { + return null; + } + switch (unit) { + case NANOSECONDS: + return ChronoUnit.NANOS; + case MICROSECONDS: + return ChronoUnit.MICROS; + case MILLISECONDS: + return ChronoUnit.MILLIS; + case SECONDS: + return ChronoUnit.SECONDS; + case MINUTES: + return ChronoUnit.MINUTES; + case HOURS: + return ChronoUnit.HOURS; + case DAYS: + return ChronoUnit.DAYS; + default: + throw new IllegalArgumentException("Unknown TimeUnit constant"); + } + } + + /** + * 转换 {@link ChronoUnit} 到 {@link TimeUnit}. + * + * @param unit {@link ChronoUnit},如果为{@code null}返回{@code null} + * @return {@link TimeUnit} + * @throws IllegalArgumentException 如果{@link TimeUnit}没有对应单位抛出 + * @since 5.7.16 + */ + public static TimeUnit toTimeUnit(ChronoUnit unit) throws IllegalArgumentException { + if (null == unit) { + return null; + } + switch (unit) { + case NANOS: + return TimeUnit.NANOSECONDS; + case MICROS: + return TimeUnit.MICROSECONDS; + case MILLIS: + return TimeUnit.MILLISECONDS; + case SECONDS: + return TimeUnit.SECONDS; + case MINUTES: + return TimeUnit.MINUTES; + case HOURS: + return TimeUnit.HOURS; + case DAYS: + return TimeUnit.DAYS; + default: + throw new IllegalArgumentException("ChronoUnit cannot be converted to TimeUnit: " + unit); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java b/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java index fd4751d6a..a33b2a4ed 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java @@ -840,6 +840,7 @@ public class Assert { /** * 检查值是否在指定范围内 * + * @param 异常类型 * @param value 值 * @param min 最小值(包含) * @param max 最大值(包含) @@ -859,9 +860,11 @@ public class Assert { /** * 检查值是否在指定范围内 * - * @param value 值 - * @param min 最小值(包含) - * @param max 最大值(包含) + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @param errorMsgTemplate 异常信息模板,类似于"aa{}bb{}cc" + * @param params 异常信息参数,用于替换"{}"占位符 * @return 经过检查后的值 * @since 5.7.15 */ @@ -885,6 +888,7 @@ public class Assert { /** * 检查值是否在指定范围内 * + * @param 异常类型 * @param value 值 * @param min 最小值(包含) * @param max 最大值(包含) @@ -904,9 +908,11 @@ public class Assert { /** * 检查值是否在指定范围内 * - * @param value 值 - * @param min 最小值(包含) - * @param max 最大值(包含) + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @param errorMsgTemplate 异常信息模板,类似于"aa{}bb{}cc" + * @param params 异常信息参数,用于替换"{}"占位符 * @return 经过检查后的值 * @since 5.7.15 */ @@ -930,6 +936,7 @@ public class Assert { /** * 检查值是否在指定范围内 * + * @param 异常类型 * @param value 值 * @param min 最小值(包含) * @param max 最大值(包含) @@ -949,9 +956,11 @@ public class Assert { /** * 检查值是否在指定范围内 * - * @param value 值 - * @param min 最小值(包含) - * @param max 最大值(包含) + * @param value 值 + * @param min 最小值(包含) + * @param max 最大值(包含) + * @param errorMsgTemplate 异常信息模板,类似于"aa{}bb{}cc" + * @param params 异常信息参数,用于替换"{}"占位符 * @return 经过检查后的值 * @since 5.7.15 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index 9f3dac1da..7a7acb17f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -262,6 +262,25 @@ public class Opt { return this; } + + /** + * 如果包裹里元素的值存在,就执行对应的操作集,并返回本身 + * 如果不存在,返回一个空的{@code Opt} + * + *

属于 {@link #ifPresent}的链式拓展 + *

属于 {@link #peek(Consumer)}的动态拓展 + * + * @param actions 值存在时执行的操作,动态参数,可传入数组,当数组为一个空数组时并不会抛出 {@code NPE} + * @return this + * @throws NullPointerException 如果值存在,并且传入的操作集中的元素为 {@code null} + * @author VampireAchao + */ + @SafeVarargs + public final Opt peeks(Consumer... actions) throws NullPointerException { + // 第三个参数 (opts, opt) -> null其实并不会执行到该函数式接口所以直接返回了个null + return Stream.of(actions).reduce(this, Opt::peek, (opts, opt) -> null); + } + /** * 如果包裹里元素的值存在,就返回本身,如果不存在,则使用传入的操作执行后获得的 {@code Opt} * diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java index b6cfa3ca8..a7cb9cd69 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java @@ -16,17 +16,17 @@ import java.util.Objects; public class Pair extends CloneSupport> implements Serializable { private static final long serialVersionUID = 1L; - private final K key; - private final V value; + protected K key; + protected V value; /** - * 构建{@link Pair}对象 + * 构建{@code Pair}对象 * * @param 键类型 * @param 值类型 * @param key 键 * @param value 值 - * @return {@link Pair} + * @return {@code Pair} * @since 5.4.3 */ public static Pair of(K key, V value) { diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java index d21b8523b..ed7f8cad6 100755 --- a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java @@ -32,9 +32,11 @@ public interface RegexPool { */ String GROUP_VAR = "\\$(\\d+)"; /** - * IP v4 + * IP v4
+ * 采用分组方式便于解析地址的每一个段 */ - String IPV4 = "\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b"; + //String IPV4 = "\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b"; + String IPV4 = "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)$"; /** * IP v6 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableDouble.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableDouble.java index 31a72d8bd..17702fbe7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableDouble.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableDouble.java @@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable; import cn.hutool.core.util.NumberUtil; /** - * 可变 double 类型 + * 可变 {@code double} 类型 * * @see Double * @since 3.0.1 @@ -150,12 +150,12 @@ public class MutableDouble extends Number implements Comparable, * 相等需同时满足如下条件: *

    *
  1. 非空
  2. - *
  3. 类型为 {@link MutableDouble}
  4. + *
  5. 类型为 {@code MutableDouble}
  6. *
  7. 值相等
  8. *
* * @param obj 比对的对象 - * @return 相同返回true,否则 false + * @return 相同返回true,否则 {@code false} */ @Override public boolean equals(final Object obj) { @@ -175,7 +175,7 @@ public class MutableDouble extends Number implements Comparable, /** * 比较 * - * @param other 其它 {@link MutableDouble} 对象 + * @param other 其它 {@code MutableDouble} 对象 * @return x==y返回0,x<y返回-1,x>y返回1 */ @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutablePair.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutablePair.java new file mode 100644 index 000000000..7e5538515 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutablePair.java @@ -0,0 +1,57 @@ +package cn.hutool.core.lang.mutable; + +import cn.hutool.core.lang.Pair; + +/** + * 可变{@link Pair}实现,可以修改键和值 + * + * @param 键类型 + * @param 值类型 + * @since 5.7.16 + */ +public class MutablePair extends Pair implements Mutable>{ + private static final long serialVersionUID = 1L; + + /** + * 构造 + * + * @param key 键 + * @param value 值 + */ + public MutablePair(K key, V value) { + super(key, value); + } + + /** + * 设置键 + * + * @param key 新键 + * @return this + */ + public MutablePair setKey(K key) { + this.key = key; + return this; + } + + /** + * 设置值 + * + * @param value 新值 + * @return this + */ + public MutablePair setValue(V value) { + this.value = value; + return this; + } + + @Override + public Pair get() { + return this; + } + + @Override + public void set(Pair pair) { + this.key = pair.getKey(); + this.value = pair.getValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java b/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java index 3b6ab8140..9b45795e6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java @@ -3,13 +3,14 @@ package cn.hutool.core.net; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.Validator; +import cn.hutool.core.lang.PatternPool; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.regex.Matcher; /** * IPV4地址工具类 @@ -20,6 +21,7 @@ import java.util.Objects; * @since 5.4.1 */ public class Ipv4Util { + /** * IP段的分割符 */ @@ -149,18 +151,25 @@ public class Ipv4Util { /** * 根据ip地址(xxx.xxx.xxx.xxx)计算出long型的数据 * 方法别名:inet_aton + * * @param strIP IP V4 地址 * @return long值 */ public static long ipv4ToLong(String strIP) { - Validator.validateIpv4(strIP, "Invalid IPv4 address!"); - final long[] ip = Convert.convert(long[].class, StrUtil.split(strIP, CharUtil.DOT)); - return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]; + final Matcher matcher = PatternPool.IPV4.matcher(strIP); + if (matcher.matches()) { + return matchAddress(matcher); + } +// Validator.validateIpv4(strIP, "Invalid IPv4 address!"); +// final long[] ip = Convert.convert(long[].class, StrUtil.split(strIP, CharUtil.DOT)); +// return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3]; + throw new IllegalArgumentException("Invalid IPv4 address!"); } /** * 根据 ip/掩码位 计算IP段的起始IP(字符串型) * 方法别名:inet_ntoa + * * @param ip 给定的IP,如218.240.38.69 * @param maskBit 给定的掩码位,如30 * @return 起始IP的字符串表示 @@ -195,9 +204,7 @@ public class Ipv4Util { * 根据子网掩码转换为掩码位 * * @param mask 掩码的点分十进制表示,例如 255.255.255.0 - * * @return 掩码位,例如 24 - * * @throws IllegalArgumentException 子网掩码非法 */ public static int getMaskBitByMask(String mask) { @@ -282,7 +289,6 @@ public class Ipv4Util { * 判断掩码是否合法 * * @param mask 掩码的点分十进制表示,例如 255.255.255.0 - * * @return true:掩码合法;false:掩码不合法 */ public static boolean isMaskValid(String mask) { @@ -293,7 +299,6 @@ public class Ipv4Util { * 判断掩码位是否合法 * * @param maskBit 掩码位,例如 24 - * * @return true:掩码位合法;false:掩码位不合法 */ public static boolean isMaskBitValid(int maskBit) { @@ -315,5 +320,19 @@ public class Ipv4Util { return getBeginIpLong(ip, maskBit) + ~ipv4ToLong(getMaskByMaskBit(maskBit)); } + + /** + * 将匹配到的Ipv4地址的4个分组分别处理 + * + * @param matcher 匹配到的Ipv4正则 + * @return ipv4对应long + */ + private static long matchAddress(Matcher matcher) { + long addr = 0; + for (int i = 1; i <= 4; ++i) { + addr |= Long.parseLong(matcher.group(i)) << 8 * (4 - i); + } + return addr; + } //-------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index ecebe74b6..7758998e5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -699,14 +699,15 @@ public class NetUtil { * @since 4.0.6 */ public static boolean isInRange(String ip, String cidr) { - String[] ips = StrUtil.splitToArray(ip, '.'); - int ipAddr = (Integer.parseInt(ips[0]) << 24) | (Integer.parseInt(ips[1]) << 16) | (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]); - int type = Integer.parseInt(cidr.replaceAll(".*/", "")); - int mask = 0xFFFFFFFF << (32 - type); - String cidrIp = cidr.replaceAll("/.*", ""); - String[] cidrIps = cidrIp.split("\\."); - int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24) | (Integer.parseInt(cidrIps[1]) << 16) | (Integer.parseInt(cidrIps[2]) << 8) | Integer.parseInt(cidrIps[3]); - return (ipAddr & mask) == (cidrIpAddr & mask); + final int maskSplitMarkIndex = cidr.lastIndexOf(Ipv4Util.IP_MASK_SPLIT_MARK); + if(maskSplitMarkIndex < 0){ + throw new IllegalArgumentException("Invalid cidr: " + cidr); + } + + final long mask = (-1L << 32 - Integer.parseInt(cidr.substring(maskSplitMarkIndex + 1))); + long cidrIpAddr = ipv4ToLong(cidr.substring(0, maskSplitMarkIndex)); + + return (ipv4ToLong(ip) & mask) == (cidrIpAddr & mask); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java new file mode 100644 index 000000000..713b082c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java @@ -0,0 +1,98 @@ +package cn.hutool.core.net; + +import cn.hutool.core.codec.PercentCodec; + +/** + * rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现 + * + * @author looly + * @since 5.7.16 + */ +public class RFC3986 { + + /** + * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + */ + public static final PercentCodec GEN_DELIMS = PercentCodec.of(":/?#[]&"); + + /** + * sub-delims = "!" / "$" / "{@code &}" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + */ + public static final PercentCodec SUB_DELIMS = PercentCodec.of("!$&'()*+,;="); + + /** + * reserved = gen-delims / sub-delims + */ + public static final PercentCodec RESERVED = GEN_DELIMS.orNew(SUB_DELIMS); + + /** + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + */ + public static final PercentCodec UNRESERVED = PercentCodec.of(unreservedChars()); + + /** + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + */ + public static final PercentCodec PCHAR = UNRESERVED.orNew(SUB_DELIMS).or(PercentCodec.of(":@")); + + /** + * segment = pchar + */ + public static final PercentCodec SEGMENT = PCHAR; + /** + * segment-nz-nc = SEGMENT ; non-zero-length segment without any colon ":" + */ + public static final PercentCodec SEGMENT_NZ_NC = PercentCodec.of(SEGMENT).removeSafe(':'); + + /** + * path = segment / "/" + */ + public static final PercentCodec PATH = SEGMENT.orNew(PercentCodec.of("/")); + + /** + * query = pchar / "/" / "?" + */ + public static final PercentCodec QUERY = PCHAR.orNew(PercentCodec.of("/?")); + + /** + * fragment = pchar / "/" / "?" + */ + public static final PercentCodec FRAGMENT = QUERY; + + /** + * query中的key + */ + public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY).removeSafe('&').removeSafe('='); + + /** + * query中的value + */ + public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(QUERY).removeSafe('&'); + + /** + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * + * @return unreserved字符 + */ + private static StringBuilder unreservedChars() { + StringBuilder sb = new StringBuilder(); + + // ALPHA + for (char c = 'A'; c <= 'Z'; c++) { + sb.append(c); + } + for (char c = 'a'; c <= 'z'; c++) { + sb.append(c); + } + + // DIGIT + for (char c = '0'; c <= '9'; c++) { + sb.append(c); + } + + // "-" / "." / "_" / "~" + sb.append("_.-~"); + + return sb; + } +} 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 be6d5c94b..708e9721a 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,6 +1,7 @@ package cn.hutool.core.net.url; 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; @@ -322,13 +323,25 @@ public final class UrlBuilder implements Serializable { } /** - * 增加路径节点 + * 增加路径,在现有路径基础上追加路径 + * + * @param path 路径,例如aaa/bbb/ccc + * @return this + */ + public UrlBuilder addPath(CharSequence path) { + UrlPath.of(path, this.charset).getSegments().forEach(this::addPathSegment); + return this; + } + + /** + * 增加路径节点,路径节点中的"/"会被转义为"%2F" * * @param segment 路径节点 * @return this + * @since 5.7.16 */ - public UrlBuilder addPath(String segment) { - if (StrUtil.isBlank(segment)) { + public UrlBuilder addPathSegment(CharSequence segment) { + if (StrUtil.isEmpty(segment)) { return this; } if (null == this.path) { @@ -341,19 +354,13 @@ public final class UrlBuilder implements Serializable { /** * 追加path节点 * - * @param segment path节点 + * @param path path节点 * @return this + * @deprecated 方法重复,请使用{@link #addPath(CharSequence)} */ - public UrlBuilder appendPath(CharSequence segment) { - if (StrUtil.isEmpty(segment)) { - return this; - } - - if (this.path == null) { - this.path = new UrlPath(); - } - this.path.add(segment); - return this; + @Deprecated + public UrlBuilder appendPath(CharSequence path) { + return addPath(path); } /** @@ -419,7 +426,7 @@ public final class UrlBuilder implements Serializable { * @return 标识符,例如#后边的部分 */ public String getFragmentEncoded() { - return URLUtil.encodeFragment(this.fragment, this.charset); + return RFC3986.FRAGMENT.encode(this.fragment, this.charset); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java index 66fc70109..bc91edf9b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java @@ -2,10 +2,10 @@ package cn.hutool.core.net.url; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.net.RFC3986; import cn.hutool.core.net.URLDecoder; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; import java.nio.charset.Charset; import java.util.LinkedList; @@ -29,7 +29,7 @@ public class UrlPath { * @param charset decode用的编码,null表示不做decode * @return UrlPath */ - public static UrlPath of(String pathStr, Charset charset) { + public static UrlPath of(CharSequence pathStr, Charset charset) { final UrlPath urlPath = new UrlPath(); urlPath.parse(pathStr, charset); return urlPath; @@ -97,7 +97,7 @@ public class UrlPath { * @param charset decode编码,null表示不解码 * @return this */ - public UrlPath parse(String path, Charset charset) { + public UrlPath parse(CharSequence path, Charset charset) { if (StrUtil.isNotEmpty(path)) { // 原URL中以/结尾,则这个规则需保留,issue#I1G44J@Gitee if(StrUtil.endWith(path, CharUtil.SLASH)){ @@ -127,7 +127,7 @@ public class UrlPath { final StringBuilder builder = new StringBuilder(); for (String segment : segments) { - builder.append(CharUtil.SLASH).append(URLUtil.encodePathSegment(segment, charset)); + builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT_NZ_NC.encode(segment, charset)); } if (withEngTag || StrUtil.isEmpty(builder)) { builder.append(CharUtil.SLASH); 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 8f882d9f5..69d5ec641 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 @@ -5,6 +5,7 @@ import cn.hutool.core.collection.IterUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.TableMap; +import cn.hutool.core.net.RFC3986; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; @@ -220,10 +221,15 @@ public class UrlQuery { } /** - * 构建URL查询字符串,即将key-value键值对转换为key1=v1&key2=&key3=v3形式 + * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
+ * 对于{@code null}处理规则如下: + *
    + *
  • 如果key为{@code null},则这个键值对忽略
  • + *
  • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
  • + *
* * @param charset encode编码,null表示不做encode编码 - * @param isEncode 是否转义键和值 + * @param isEncode 是否转义键和值,转义遵循rfc3986规范 * @return URL查询字符串 * @since 5.7.13 */ @@ -233,21 +239,18 @@ public class UrlQuery { } final StringBuilder sb = new StringBuilder(); - boolean isFirst = true; - CharSequence key; + CharSequence name; CharSequence value; for (Map.Entry entry : this.query) { - if (isFirst) { - isFirst = false; - } else { - sb.append("&"); - } - key = entry.getKey(); - if (null != key) { - sb.append(toStr(key, charset, isEncode)); + name = entry.getKey(); + if (null != name) { + if(sb.length() >0){ + sb.append("&"); + } + sb.append(isEncode ? RFC3986.QUERY_PARAM_NAME.encode(name, charset) : name); value = entry.getValue(); if (null != value) { - sb.append("=").append(toStr(value, charset, isEncode)); + sb.append("=").append(isEncode ? RFC3986.QUERY_PARAM_VALUE.encode(value, charset) : value); } } } @@ -301,18 +304,18 @@ public class UrlQuery { } /** - * 键值对的{@link CharSequence}转换为String,可选是否转义 + * 键值对的name转换为 * * @param str 原字符串 * @param charset 编码,只用于encode中 - * @param isEncode 是否转义 + * @param isEncode 是否转义,转义遵循rfc3986规范 * @return 转换后的String * @since 5.7.13 */ - private static String toStr(CharSequence str, Charset charset, boolean isEncode) { + private static String nameToStr(CharSequence str, Charset charset, boolean isEncode) { String result = StrUtil.str(str); if (isEncode) { - result = URLUtil.encodeFragment(result, charset); + result = RFC3986.QUERY_PARAM_NAME.encode(result, charset); } return result; } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 1a41d7282..511fb3c6e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -18,6 +18,7 @@ import cn.hutool.core.util.StrUtil; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.text.MessageFormat; +import java.text.Normalizer; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; @@ -2856,10 +2857,10 @@ public class CharSequenceUtil { len += str.length(); } if (isNotEmpty(prefix)) { - len += str.length(); + len += prefix.length(); } if (isNotEmpty(suffix)) { - len += str.length(); + len += suffix.length(); } StringBuilder sb = new StringBuilder(len); if (isNotEmpty(prefix) && false == startWith(str, prefix)) { @@ -4338,7 +4339,21 @@ public class CharSequenceUtil { * @return 给定字符串的所有字符是否都一样 * @since 5.7.3 */ - public static boolean isCharEquals(String str) { - return isBlank(str.replace(str.charAt(0), CharUtil.SPACE)); + public static boolean isCharEquals(CharSequence str) { + Assert.notEmpty(str, "Str to check must be not empty!"); + return count(str, str.charAt(0)) == str.length(); + } + + /** + * 对字符串归一化处理,如 "Á" 可以使用 "u00C1"或 "u0041u0301"表示,实际测试中两个字符串并不equals
+ * 因此使用此方法归一为一种表示形式,默认按照W3C通常建议的,在NFC中交换文本。 + * + * @param str 归一化的字符串 + * @return 归一化后的字符串 + * @see Normalizer#normalize(CharSequence, Normalizer.Form) + * @since 5.7.16 + */ + public static String normalize(CharSequence str) { + return Normalizer.normalize(str, Normalizer.Form.NFC); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java b/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java index 0de657e51..69bc75f04 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java @@ -579,7 +579,7 @@ public class StrBuilder implements CharSequence, Appendable, Serializable { private static int totalLength(CharSequence... strs) { int totalLength = 0; for (CharSequence str : strs) { - totalLength += (null == str ? 4 : str.length()); + totalLength += (null == str ? 0 : str.length()); } return totalLength; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 5912fd56b..adc11b2f8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -22,7 +22,6 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.function.Function; @@ -118,7 +117,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { public static boolean hasNull(T... array) { if (isNotEmpty(array)) { for (T element : array) { - if (null == element) { + if (ObjectUtil.isNull(element)) { return true; } } @@ -150,7 +149,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { */ @SuppressWarnings("unchecked") public static T firstNonNull(T... array) { - return firstMatch(Objects::nonNull, array); + return firstMatch(ObjectUtil::isNotNull, array); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java index c5bf25e57..08f30132a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java @@ -1675,12 +1675,12 @@ public class NumberUtil { * * @param x 第一个值 * @param y 第二个值 - * @return x==y返回0,x<y返回-1,x>y返回1 + * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数 * @see Character#compare(char, char) * @since 3.0.1 */ public static int compare(char x, char y) { - return x - y; + return Character.compare(x, y); } /** @@ -1688,7 +1688,7 @@ public class NumberUtil { * * @param x 第一个值 * @param y 第二个值 - * @return x==y返回0,x<y返回-1,x>y返回1 + * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数 * @see Double#compare(double, double) * @since 3.0.1 */ @@ -1701,7 +1701,7 @@ public class NumberUtil { * * @param x 第一个值 * @param y 第二个值 - * @return x==y返回0,x<y返回-1,x>y返回1 + * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数 * @see Integer#compare(int, int) * @since 3.0.1 */ @@ -1714,7 +1714,7 @@ public class NumberUtil { * * @param x 第一个值 * @param y 第二个值 - * @return x==y返回0,x<y返回-1,x>y返回1 + * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数 * @see Long#compare(long, long) * @since 3.0.1 */ @@ -1727,7 +1727,7 @@ public class NumberUtil { * * @param x 第一个值 * @param y 第二个值 - * @return x==y返回0,x<y返回-1,x>y返回1 + * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数 * @see Short#compare(short, short) * @since 3.0.1 */ @@ -1754,7 +1754,7 @@ public class NumberUtil { * @param bigNum1 数字1 * @param bigNum2 数字2 * @return 是否大于 - * @since 3, 0.9 + * @since 3.0.9 */ public static boolean isGreater(BigDecimal bigNum1, BigDecimal bigNum2) { Assert.notNull(bigNum1); diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java index 649e18e19..eeab6d5fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java @@ -292,7 +292,7 @@ public class ObjectUtil { * @since 3.0.7 */ public static T defaultIfNull(final T object, final T defaultValue) { - return (null != object) ? object : defaultValue; + return isNull(object) ? defaultValue : object; } @@ -300,14 +300,14 @@ public class ObjectUtil { * 如果给定对象为{@code null} 返回默认值, 如果不为null 返回自定义handle处理后的返回值 * * @param source Object 类型对象 - * @param handle 自定义的处理方法 + * @param handle 非空时自定义的处理方法 * @param defaultValue 默认为空的返回值 * @param 被检查对象为{@code null}返回默认值,否则返回自定义handle处理后的返回值 * @return 处理后的返回值 * @since 5.4.6 */ public static T defaultIfNull(Object source, Supplier handle, final T defaultValue) { - if (Objects.nonNull(source)) { + if (isNotNull(source)) { return handle.get(); } return defaultValue; @@ -455,11 +455,14 @@ public class ObjectUtil { /** * 是否为基本类型,包括包装类型和非包装类型 * - * @param object 被检查对象 + * @param object 被检查对象,{@code null}返回{@code false} * @return 是否为基本类型 * @see ClassUtil#isBasicType(Class) */ public static boolean isBasicType(Object object) { + if (null == object) { + return false; + } return ClassUtil.isBasicType(object.getClass()); } 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 new file mode 100644 index 000000000..d22679567 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java @@ -0,0 +1,146 @@ +package cn.hutool.core.util; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Console; + +import java.util.Properties; + +/** + * 系统属性工具
+ * 此工具用于读取系统属性或环境变量信息,封装包括: + *
    + *
  • {@link System#getProperty(String)}
  • + *
  • {@link System#getenv(String)}
  • + *
+ * + * @author looly + * @since 5.7.16 + */ +public class SystemPropsUtil { + + /** Hutool自定义系统属性:是否解析日期字符串采用严格模式 */ + public static String HUTOOL_DATE_LENIENT = "hutool.date.lenient"; + + /** + * 取得系统属性,如果因为Java安全的限制而失败,则将错误打在Log中,然后返回 defaultValue + * + * @param name 属性名 + * @param defaultValue 默认值 + * @return 属性值或defaultValue + * @see System#getProperty(String) + * @see System#getenv(String) + */ + public static String get(String name, String defaultValue) { + return StrUtil.nullToDefault(get(name, false), defaultValue); + } + + /** + * 取得系统属性,如果因为Java安全的限制而失败,则将错误打在Log中,然后返回 {@code null} + * + * @param name 属性名 + * @param quiet 安静模式,不将出错信息打在{@code System.err}中 + * @return 属性值或{@code null} + * @see System#getProperty(String) + * @see System#getenv(String) + */ + public static String get(String name, boolean quiet) { + String value = null; + try { + value = System.getProperty(name); + } catch (SecurityException e) { + if (false == quiet) { + Console.error("Caught a SecurityException reading the system property '{}'; " + + "the SystemUtil property value will default to null.", name); + } + } + + if (null == value) { + try { + value = System.getenv(name); + } catch (SecurityException e) { + if (false == quiet) { + Console.error("Caught a SecurityException reading the system env '{}'; " + + "the SystemUtil env value will default to null.", name); + } + } + } + + return value; + } + + /** + * 获得System属性 + * + * @param key 键 + * @return 属性值 + * @see System#getProperty(String) + * @see System#getenv(String) + */ + public static String get(String key) { + return get(key, null); + } + + /** + * 获得boolean类型值 + * + * @param key 键 + * @param defaultValue 默认值 + * @return 值 + */ + public static boolean getBoolean(String key, boolean defaultValue) { + String value = get(key); + if (value == null) { + return defaultValue; + } + + value = value.trim().toLowerCase(); + if (value.isEmpty()) { + return true; + } + + return Convert.toBool(value, defaultValue); + } + + /** + * 获得int类型值 + * + * @param key 键 + * @param defaultValue 默认值 + * @return 值 + */ + public static long getInt(String key, int defaultValue) { + return Convert.toInt(get(key), defaultValue); + } + + /** + * 获得long类型值 + * + * @param key 键 + * @param defaultValue 默认值 + * @return 值 + */ + public static long getLong(String key, long defaultValue) { + return Convert.toLong(get(key), defaultValue); + } + + /** + * @return 属性列表 + */ + public static Properties getProps() { + return System.getProperties(); + } + + /** + * 设置系统属性,value为{@code null}表示移除此属性 + * + * @param key 属性名 + * @param value 属性值,{@code null}表示移除此属性 + */ + public static void set(String key, String value) { + if (null == value) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java b/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java new file mode 100644 index 000000000..e093d3b96 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/util/W84Util.java @@ -0,0 +1,172 @@ +package cn.hutool.core.util; + +/** + * 坐标转换相关工具类
+ * + * 坐标转换相关参考网址: https://tool.lu/coordinate/ + * @author hongzhe.qin + * @email qin462328037@163.com + * @since 6.0 + */ +public class W84Util { + + /** + * 坐标转换参数:(暂时位置具体名称) + */ + public static final Double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + + /** + * 坐标转换参数:π + */ + public static final Double PI = 3.1415926535897932384626; + + /** + * 地球半径 + */ + public static final Double RADIUS = 6378245.0; + + /** + * 修正参数 + */ + public static final Double CORRECTION_PARAM = 0.00669342162296594323; + + + /** + * 计算维度坐标 + * + * @param lng 经度 + * @param lat 维度 + * @return ret 计算完成后的 + */ + private static Double transForMLat(double lng, double lat) { + Double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret = transCore(ret, lng, lat); + ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; + return ret; + } + + /** + * 计算经度坐标 + * + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return ret 计算完成后的 + */ + private static Double transForMLng(Double lng, Double lat) { + Double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret = transCore(ret, lng, lat); + ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; + return ret; + } + + /** + * 转换坐标公共核心 + * + * @param ret 计算需要返回结果 + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return 返回结果 + */ + private static Double transCore(Double ret, Double lng, Double lat) { + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; + return ret; + } + + private static Double transW84Core(Double lat){ + return 1 - CORRECTION_PARAM * Math.sin(lat / 180.0 * PI) * Math.sin(lat / 180.0 * PI); + } + + /** + * 火星坐标系 (GCJ-02) 核心计算 + * @param lng 经度值 + * @param lat 纬度值 + * @return 坐标 + */ + private static Double[] transGCJ02Core(Double lng,Double lat){ + Double dlat = transForMLat(lng - 105.0, lat - 35.0); + Double dlng = transForMLng(lng - 105.0, lat - 35.0); + Double magic = transW84Core(lat); + Double sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtmagic) * PI); + dlng = (dlng * 180.0) / (RADIUS / sqrtmagic * Math.cos(lat / 180.0 * PI) * PI); + Double mglat = lat + dlat; + Double mglng = lng + dlng; + return new Double[]{mglng,mglat}; + } + + /** + * WGS84 坐标转为 百度坐标系 (BD-09) 坐标 + * @param lng 经度值 + * @param lat 维度值 + * @return bd09 坐标 + */ + public static Double[] wgs84tobd09(Double lng, Double lat) { + // 第一次转换 + Double dlat = transForMLat(lng - 105.0, lat - 35.0); + Double dlng = transForMLng(lng - 105.0, lat - 35.0); + Double magic = transW84Core(lat); + Double sqrtmagic = Math.sqrt(1 - CORRECTION_PARAM * Math.sin(lat / 180.0 * PI) * Math.sin(lat / 180.0 * PI)); + Double mglat = lat + (dlat * 180.0) / ((RADIUS * (1 - CORRECTION_PARAM)) / (magic * sqrtmagic) * PI); + Double mglng = lng + (dlng * 180.0) / (RADIUS / sqrtmagic * Math.cos(lat / 180.0 * PI) * PI); + // 第二次转换 + Double z = Math.sqrt(mglng * mglng + mglat * mglat) + 0.00002 * Math.sin(mglat * X_PI); + Double theta = Math.atan2(mglat, mglng) + 0.000003 * Math.cos(mglng * X_PI); + Double bd_lng = z * Math.cos(theta) + 0.0065; + Double bd_lat = z * Math.sin(theta) + 0.006; + return new Double[]{bd_lng,bd_lat}; + } + + /** + * WGS84 转换为 火星坐标系 (GCJ-02) + * + * @param lng + * @param lat + * @returns {*[]} + */ + public static Double[] wgs84togcj02(Double lng, Double lat) { + return transGCJ02Core(lng,lat); + } + + /** + * 火星坐标系 (GCJ-02) 转换为 WGS84 + * @param lng 经度坐标 + * @param lat 维度坐标 + * @return WGS84 坐标 + */ + public static Double[] gcj02towgs84(Double lng, Double lat) { + return transGCJ02Core(lng,lat); + } + + /** + * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 + * 即 百度 转 谷歌、高德 + * @param bd_lon 经度值 + * @param bd_lat 纬度值 + * @return GCJ-02 坐标 + */ + public static Double[] bd09togcj02(Double bd_lon, Double bd_lat) { + Double x = bd_lon - 0.0065; + Double y = bd_lat - 0.006; + Double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); + Double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); + Double gg_lng = z * Math.cos(theta); + Double gg_lat = z * Math.sin(theta); + return new Double[]{gg_lng,gg_lat}; + } + + /** + * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 + * @param lng 经度值 + * @param lat 纬度值 + * @return BD-09 坐标 + */ + public static Double[] gcj02tobd09(Double lng, Double lat) { + double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); + double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); + double bd_lng = z * Math.cos(theta) + 0.0065; + double bd_lat = z * Math.sin(theta) + 0.006; + return new Double[]{bd_lng,bd_lat}; + } + +} 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 a813242bf..37e77b5d5 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 @@ -91,9 +91,10 @@ public class ZipUtil { * @param zipPath zip文件的Path * @param appendFilePath 待添加文件Path(可以是文件夹) * @param options 拷贝选项,可选是否覆盖等 + * @throws IORuntimeException IO异常 * @since 5.7.15 */ - public static void append(Path zipPath, Path appendFilePath, CopyOption... options) throws IOException { + public static void append(Path zipPath, Path appendFilePath, CopyOption... options) throws IORuntimeException { try (FileSystem zipFileSystem = FileSystemUtil.createZip(zipPath.toString())) { if (Files.isDirectory(appendFilePath)) { Path source = appendFilePath.getParent(); @@ -107,6 +108,8 @@ public class ZipUtil { } } catch (FileAlreadyExistsException ignored) { // 不覆盖情况下,文件已存在, 跳过 + } catch (IOException e){ + throw new IORuntimeException(e); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 9c52e98fe..8e7f5da2f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -63,6 +63,34 @@ public class OptTest { Assert.assertEquals("hutool", name); } + @Test + public void peeksTest() { + User user = new User(); + // 相当于上面peek的动态参数调用,更加灵活,你可以像操作数组一样去动态设置中间的步骤,也可以使用这种方式去编写你的代码 + // 可以一行搞定 + Opt.ofNullable("hutool").peeks(user::setUsername, user::setNickname); + // 也可以在适当的地方换行使得代码的可读性提高 + Opt.of(user).peeks( + u -> Assert.assertEquals("hutool", u.getNickname()), + u -> Assert.assertEquals("hutool", u.getUsername()) + ); + Assert.assertEquals("hutool", user.getNickname()); + Assert.assertEquals("hutool", user.getUsername()); + + // 注意,传入的lambda中,对包裹内的元素执行赋值操作并不会影响到原来的元素,这是java语言的特性。。。 + // 这也是为什么我们需要getter和setter而不直接给bean中的属性赋值中的其中一个原因 + String name = Opt.ofNullable("hutool").peeks( + username -> username = "123", username -> username = "456", + n -> Assert.assertEquals("hutool", n)).get(); + Assert.assertEquals("hutool", name); + + // 当然,以下情况不会抛出NPE,但也没什么意义 + Opt.ofNullable("hutool").peeks().peeks().peeks(); + Opt.ofNullable(null).peeks(i -> { + }); + + } + @Test public void orTest() { // 这是jdk9 Optional中的新函数,直接照搬了过来 diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java index a14a3d8dd..cfcd7866f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java @@ -21,7 +21,7 @@ public class ValidatorTest { } @Test - public void hasNumberTest() throws Exception { + public void hasNumberTest() { String var1 = ""; String var2 = "str"; String var3 = "180"; @@ -218,4 +218,13 @@ public class ValidatorTest { public void isCarDrivingLicenceTest(){ Assert.assertTrue(Validator.isCarDrivingLicence("430101758218")); } + + @Test + public void validateIpv4Test(){ + Validator.validateIpv4("192.168.1.1", "Error ip"); + Validator.validateIpv4("8.8.8.8", "Error ip"); + Validator.validateIpv4("0.0.0.0", "Error ip"); + Validator.validateIpv4("255.255.255.255", "Error ip"); + Validator.validateIpv4("127.0.0.0", "Error ip"); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/net/Ipv4UtilTest.java b/hutool-core/src/test/java/cn/hutool/core/net/Ipv4UtilTest.java index 5a472963c..13d5ddbd1 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/Ipv4UtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/Ipv4UtilTest.java @@ -1,11 +1,10 @@ package cn.hutool.core.net; -import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; +import org.junit.function.ThrowingRunnable; import java.util.List; -import org.junit.function.ThrowingRunnable; public class Ipv4UtilTest { @@ -40,7 +39,7 @@ public class Ipv4UtilTest { String ip = "192.168.1.1"; final int maskBitByMask = Ipv4Util.getMaskBitByMask("255.255.255.0"); final String endIpStr = Ipv4Util.getEndIpStr(ip, maskBitByMask); - Console.log(endIpStr); + Assert.assertEquals("192.168.1.255", endIpStr); } @Test @@ -75,4 +74,16 @@ public class Ipv4UtilTest { boolean maskBitValid = Ipv4Util.isMaskBitValid(33); Assert.assertFalse("掩码位非法检验", maskBitValid); } + + @Test + public void ipv4ToLongTest(){ + long l = Ipv4Util.ipv4ToLong("127.0.0.1"); + Assert.assertEquals(2130706433L, l); + l = Ipv4Util.ipv4ToLong("114.114.114.114"); + Assert.assertEquals(1920103026L, l); + l = Ipv4Util.ipv4ToLong("0.0.0.0"); + Assert.assertEquals(0L, l); + l = Ipv4Util.ipv4ToLong("255.255.255.255"); + Assert.assertEquals(4294967295L, l); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java index ff467456a..0bed82723 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java @@ -101,4 +101,15 @@ public class NetUtilTest { Console.log(txt); } + @Test + public void isInRangeTest(){ + Assert.assertTrue(NetUtil.isInRange("114.114.114.114","0.0.0.0/0")); + Assert.assertTrue(NetUtil.isInRange("192.168.3.4","192.0.0.0/8")); + Assert.assertTrue(NetUtil.isInRange("192.168.3.4","192.168.0.0/16")); + Assert.assertTrue(NetUtil.isInRange("192.168.3.4","192.168.3.0/24")); + Assert.assertTrue(NetUtil.isInRange("192.168.3.4","192.168.3.4/32")); + Assert.assertFalse(NetUtil.isInRange("8.8.8.8","192.0.0.0/8")); + Assert.assertFalse(NetUtil.isInRange("114.114.114.114","192.168.3.4/32")); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index 78e4d93ee..ea7604161 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -281,4 +281,29 @@ public class UrlBuilderTest { final UrlBuilder urlBuilder = UrlBuilder.ofHttp(url); Assert.assertEquals(url, urlBuilder.toString()); } + + @Test + public void addPathEncodeTest(){ + String url = UrlBuilder.create() + .setScheme("https") + .setHost("domain.cn") + .addPath("api") + .addPath("xxx") + .addPath("bbb") + .build(); + + Assert.assertEquals("https://domain.cn/api/xxx/bbb", url); + } + + @Test + public void addPathEncodeTest2(){ + // https://github.com/dromara/hutool/issues/1912 + String url = UrlBuilder.create() + .setScheme("https") + .setHost("domain.cn") + .addPath("/api/xxx/bbb") + .build(); + + Assert.assertEquals("https://domain.cn/api/xxx/bbb", url); + } } 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 d4ab80682..85d11ec00 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 @@ -63,4 +63,40 @@ public class UrlQueryTest { query = URLUtil.buildQuery(map, StandardCharsets.UTF_8); Assert.assertEquals("password=123456&username=SSM", query); } + + @Test + public void buildHasNullTest() { + Map map = new LinkedHashMap<>(); + map.put(null, "SSM"); + map.put("password", "123456"); + String query = URLUtil.buildQuery(map, StandardCharsets.UTF_8); + Assert.assertEquals("password=123456", query); + + map = new TreeMap<>(); + map.put("username", "SSM"); + map.put("password", ""); + query = URLUtil.buildQuery(map, StandardCharsets.UTF_8); + Assert.assertEquals("password=&username=SSM", query); + + map = new TreeMap<>(); + map.put("username", "SSM"); + map.put("password", null); + query = URLUtil.buildQuery(map, StandardCharsets.UTF_8); + Assert.assertEquals("password&username=SSM", query); + } + + @Test + public void buildSpecialTest() { + Map map = new LinkedHashMap<>(); + map.put("key1&", "SSM"); + map.put("key2", "123456&"); + String query = URLUtil.buildQuery(map, StandardCharsets.UTF_8); + Assert.assertEquals("key1%26=SSM&key2=123456%26", query); + + map = new TreeMap<>(); + map.put("username=", "SSM"); + map.put("password", "="); + query = URLUtil.buildQuery(map, StandardCharsets.UTF_8); + Assert.assertEquals("password==&username%3D=SSM", query); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java index bec53ad4d..85a35a18c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java @@ -33,5 +33,19 @@ public class CharSequenceUtilTest { Assert.assertEquals( str + " is Good", result); } + @Test + public void normalizeTest(){ + // https://blog.csdn.net/oscar999/article/details/105326270 + + String str1 = "\u00C1"; + String str2 = "\u0041\u0301"; + + Assert.assertNotEquals(str1, str2); + + str1 = CharSequenceUtil.normalize(str1); + str2 = CharSequenceUtil.normalize(str2); + Assert.assertEquals(str1, str2); + } + // ------------------------------------------------------------------------ remove } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java new file mode 100644 index 000000000..453fb32ea --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/util/W84UtilTest.java @@ -0,0 +1,39 @@ +package cn.hutool.core.util; +import org.junit.Test; + +/** + * 坐标转换工具类单元测试 + * + * ps: 坐标转换存在一定误差,故此工具类无单元测试,无法验证,请根据实际业务判断误差值 + * + * @author hongzhe.qin + */ +public class W84UtilTest { + + + @Test + public void gcj02tobd09Test() { + + } + + @Test + public void bd09togcj02(){ + + } + + @Test + public void gcj02towgs84(){ + + } + + @Test + public void wgs84togcj02(){ + + } + + @Test + public void wgs84tobd09(){ + + } + +} diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 62dc9962c..4d3a77ef2 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 3a89f935d..b58ed2229 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestUtil.java index 775d6183c..fc6fd61ce 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestUtil.java @@ -1,13 +1,12 @@ package cn.hutool.crypto.digest; +import cn.hutool.core.util.CharsetUtil; + +import javax.crypto.SecretKey; import java.io.File; import java.io.InputStream; import java.nio.charset.Charset; -import javax.crypto.SecretKey; - -import cn.hutool.core.util.CharsetUtil; - /** * 摘要算法工具类 * @@ -424,7 +423,7 @@ public class DigestUtil { * 创建HMac对象,调用digest方法可获得hmac值 * * @param algorithm {@link HmacAlgorithm} - * @param key 密钥,如果为null生成随机密钥 + * @param key 密钥,如果为{@code null}生成随机密钥 * @return {@link HMac} * @since 3.0.3 */ @@ -436,7 +435,7 @@ public class DigestUtil { * 创建HMac对象,调用digest方法可获得hmac值 * * @param algorithm {@link HmacAlgorithm} - * @param key 密钥{@link SecretKey},如果为null生成随机密钥 + * @param key 密钥{@link SecretKey},如果为{@code null}生成随机密钥 * @return {@link HMac} * @since 3.0.3 */ diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index dcfd6d332..99574b86d 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 65d431f9e..2e73f8080 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 9d7626fcf..fd49b4779 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-extra @@ -27,7 +27,7 @@ 1.6.2 0.1.55 3.4.1 - 3.7.2 + 3.8.0 5.1.1 4.0.1 2.5.6 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 81f74df28..f94abd94c 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 @@ -489,9 +489,9 @@ public class Ftp extends AbstractFtp { * 上传文件到指定目录,可选: * *
-	 * 1. path为null或""上传到当前路径
-	 * 2. path为相对路径则相对于当前路径的子路径
-	 * 3. path为绝对路径则上传到此路径
+	 * 1. destPath为null或""上传到当前路径
+	 * 2. destPath为相对路径则相对于当前路径的子路径
+	 * 3. destPath为绝对路径则上传到此路径
 	 * 
* * @param file 文件 @@ -512,9 +512,9 @@ public class Ftp extends AbstractFtp { * 上传文件到指定目录,可选: * *
-	 * 1. path为null或""上传到当前路径
-	 * 2. path为相对路径则相对于当前路径的子路径
-	 * 3. path为绝对路径则上传到此路径
+	 * 1. destPath为null或""上传到当前路径
+	 * 2. destPath为相对路径则相对于当前路径的子路径
+	 * 3. destPath为绝对路径则上传到此路径
 	 * 
* * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java index a4e015cf3..64eddb20a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java @@ -17,11 +17,11 @@ import java.util.List; * @since 3.2.3 */ public class InternalMailUtil { - + /** * 将多个字符串邮件地址转为{@link InternetAddress}列表
* 单个字符串地址可以是多个地址合并的字符串 - * + * * @param addrStrs 地址数组 * @param charset 编码(主要用于中文用户名的编码) * @return 地址数组 @@ -38,12 +38,12 @@ public class InternalMailUtil { } return resultList.toArray(new InternetAddress[0]); } - + /** * 解析第一个地址 - * + * * @param address 地址字符串 - * @param charset 编码 + * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码 * @return 地址列表 */ public static InternetAddress parseFirstAddress(String address, Charset charset) { @@ -61,9 +61,9 @@ public class InternalMailUtil { /** * 将一个地址字符串解析为多个地址
* 地址间使用" "、","、";"分隔 - * + * * @param address 地址字符串 - * @param charset 编码 + * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码 * @return 地址列表 */ public static InternetAddress[] parseAddress(String address, Charset charset) { @@ -75,9 +75,10 @@ public class InternalMailUtil { } //编码用户名 if (ArrayUtil.isNotEmpty(addresses)) { + final String charsetStr = null == charset ? null : charset.name(); for (InternetAddress internetAddress : addresses) { try { - internetAddress.setPersonal(internetAddress.getPersonal(), charset.name()); + internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr); } catch (UnsupportedEncodingException e) { throw new MailException(e); } @@ -90,7 +91,7 @@ public class InternalMailUtil { /** * 编码中文字符
* 编码失败返回原字符串 - * + * * @param text 被编码的文本 * @param charset 编码 * @return 编码后的结果 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java index 1f28d16a9..2d71b2dcc 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java @@ -21,6 +21,7 @@ import javax.mail.Transport; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimeUtility; import javax.mail.util.ByteArrayDataSource; import java.io.File; import java.io.IOException; @@ -36,6 +37,7 @@ import java.util.Date; * @since 3.2.0 */ public class Mail implements Builder { + private static final long serialVersionUID = 1L; /** * 邮箱帐户信息以及一些客户端配置信息 @@ -262,7 +264,10 @@ public class Mail implements Builder { for (DataSource attachment : attachments) { bodyPart = new MimeBodyPart(); bodyPart.setDataHandler(new DataHandler(attachment)); - nameEncoded = InternalMailUtil.encodeText(attachment.getName(), charset); + nameEncoded = attachment.getName(); + if (this.mailAccount.isEncodefilename()) { + nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset); + } // 普通附件文件名 bodyPart.setFileName(nameEncoded); if (StrUtil.startWith(attachment.getContentType(), "image/")) { @@ -384,7 +389,7 @@ public class Mail implements Builder { try { return doSend(); } catch (MessagingException e) { - if(e instanceof SendFailedException){ + if (e instanceof SendFailedException) { // 当地址无效时,显示更加详细的无效地址信息 final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses(); final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses)); @@ -426,7 +431,7 @@ public class Mail implements Builder { msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset)); } // 标题 - msg.setSubject(this.title, charset.name()); + msg.setSubject(this.title, (null == charset) ? null : charset.name()); // 发送时间 msg.setSentDate(new Date()); // 内容和附件 @@ -452,14 +457,15 @@ public class Mail implements Builder { /** * 构建邮件信息主体 * - * @param charset 编码 + * @param charset 编码,{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()} * @return 邮件信息主体 * @throws MessagingException 消息异常 */ private Multipart buildContent(Charset charset) throws MessagingException { + final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset(); // 正文 final MimeBodyPart body = new MimeBodyPart(); - body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charset)); + body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr)); this.multipart.addBodyPart(body); return this.multipart; @@ -474,7 +480,7 @@ public class Mail implements Builder { private Session getSession() { final Session session = MailUtil.getSession(this.mailAccount, this.useGlobalSession); - if(null != this.debugOutput){ + if (null != this.debugOutput) { session.setDebugOut(debugOutput); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java index 925550241..f2d3aef9a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java @@ -34,8 +34,13 @@ public class MailAccount implements Serializable { private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback"; private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port"; - private static final String MAIL_DEBUG = "mail.debug"; + // System Properties private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters"; + //private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename"; + //private static final String CHARSET = "mail.mime.charset"; + + // 其他 + private static final String MAIL_DEBUG = "mail.debug"; public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"}; @@ -75,7 +80,11 @@ public class MailAccount implements Serializable { /** * 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) */ - private boolean splitlongparameters; + private boolean splitlongparameters = false; + /** + * 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + */ + private boolean encodefilename = true; /** * 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 @@ -297,16 +306,19 @@ public class MailAccount implements Serializable { /** * 获取字符集编码 * - * @return 编码 + * @return 编码,可能为{@code null} */ public Charset getCharset() { return charset; } /** - * 设置字符集编码 + * 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@code null}并设置: + *
+	 * 	System.setProperty("mail.mime.charset", charset);
+	 * 
* - * @param charset 字符集编码 + * @param charset 字符集编码,{@code null} 则表示使用全局设置的默认编码,全局编码为mail.mime.charset系统属性 * @return this */ public MailAccount setCharset(Charset charset) { @@ -324,7 +336,11 @@ public class MailAccount implements Serializable { } /** - * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) + * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
+ * 注意此项为全局设置,此项会调用 + *
+	 * System.setProperty("mail.mime.splitlongparameters", true)
+	 * 
* * @param splitlongparameters 对于超长参数是否切分为多份 */ @@ -332,6 +348,32 @@ public class MailAccount implements Serializable { this.splitlongparameters = splitlongparameters; } + /** + * 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + * + * @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true} + * @since 5.7.16 + */ + public boolean isEncodefilename() { + + return encodefilename; + } + + /** + * 设置对于文件名是否使用{@link #charset}编码,此选项不会修改全局配置
+ * 如果此选项设置为{@code false},则是否编码取决于两个系统属性: + *
    + *
  • mail.mime.encodefilename 是否编码附件文件名
  • + *
  • mail.mime.charset 编码文件名的编码
  • + *
+ * + * @param encodefilename 对于文件名是否使用{@link #charset}编码 + * @since 5.7.16 + */ + public void setEncodefilename(boolean encodefilename) { + this.encodefilename = encodefilename; + } + /** * 是否使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 * @@ -374,6 +416,7 @@ public class MailAccount implements Serializable { /** * 获取SSL协议,多个协议用空格分隔 + * * @return SSL协议,多个协议用空格分隔 * @since 5.5.7 */ @@ -565,8 +608,9 @@ public class MailAccount implements Serializable { this.host = StrUtil.format("smtp.{}", StrUtil.subSuf(fromAddress, fromAddress.indexOf('@') + 1)); } if (StrUtil.isBlank(user)) { - // 如果用户名为空,默认为发件人邮箱前缀 - this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@')); + // 如果用户名为空,默认为发件人(issue#I4FYVY@Gitee) + //this.user = StrUtil.subPre(fromAddress, fromAddress.indexOf('@')); + this.user = fromAddress; } if (null == this.auth) { // 如果密码非空白,则使用认证模式 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java index f9b800282..36e048d2b 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java @@ -313,9 +313,10 @@ public class QrCodeUtil { // 默认配置 config = new QrConfig(); } + BitMatrix bitMatrix; try { - bitMatrix = multiFormatWriter.encode(content, format, config.width, config.height, config.toHints()); + bitMatrix = multiFormatWriter.encode(content, format, config.width, config.height, config.toHints(format)); } catch (WriterException e) { throw new QrCodeException(e); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java index f9aae03e7..0c2789c87 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java @@ -3,6 +3,7 @@ package cn.hutool.extra.qrcode; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.CharsetUtil; +import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; @@ -331,13 +332,31 @@ public class QrConfig { * @return 配置 */ public HashMap toHints() { + return toHints(BarcodeFormat.QR_CODE); + } + + /** + * 转换为Zxing的二维码配置 + * + * @param format 格式,根据格式不同,{@link #errorCorrection}的值类型有所不同 + * @return 配置 + */ + public HashMap toHints(BarcodeFormat format) { // 配置 final HashMap hints = new HashMap<>(); if (null != this.charset) { hints.put(EncodeHintType.CHARACTER_SET, charset.toString().toLowerCase()); } if (null != this.errorCorrection) { - hints.put(EncodeHintType.ERROR_CORRECTION, this.errorCorrection); + Object value; + if(BarcodeFormat.AZTEC == format || BarcodeFormat.PDF_417 == format){ + // issue#I4FE3U@Gitee + value = this.errorCorrection.getBits(); + } else { + value = this.errorCorrection; + } + + hints.put(EncodeHintType.ERROR_CORRECTION, value); } if (null != this.margin) { hints.put(EncodeHintType.MARGIN, this.margin); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index a38b0c897..c553b33c8 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -17,6 +17,7 @@ import com.jcraft.jsch.SftpException; import com.jcraft.jsch.SftpProgressMonitor; import java.io.File; +import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; @@ -337,7 +338,7 @@ public class Sftp extends AbstractFtp { try { sftpATTRS = this.channel.stat(dir); } catch (SftpException e) { - if(e.getMessage().contains("No such file")){ + if (e.getMessage().contains("No such file")) { // 文件不存在直接返回false // pr#378@Gitee return false; @@ -464,6 +465,27 @@ public class Sftp extends AbstractFtp { return true; } + /** + * 上传文件到指定目录,可选: + * + *
+	 * 1. path为null或""上传到当前路径
+	 * 2. path为相对路径则相对于当前路径的子路径
+	 * 3. path为绝对路径则上传到此路径
+	 * 
+ * + * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param fileName 文件名 + * @param fileStream 文件流 + * @return 是否上传成功 + * @since 5.7.16 + */ + public boolean upload(String destPath, String fileName, InputStream fileStream) { + destPath = StrUtil.addSuffixIfNot(destPath, StrUtil.SLASH) + StrUtil.removePrefix(fileName, StrUtil.SLASH); + put(fileStream, destPath, null, Mode.OVERWRITE); + return true; + } + /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。覆盖模式 * @@ -506,6 +528,25 @@ public class Sftp extends AbstractFtp { return this; } + /** + * 将本地数据流上传到目标服务器,目标文件名为destPath,目标必须为文件 + * + * @param srcStream 本地的数据流 + * @param destPath 目标路径, + * @param monitor 上传进度监控,通过实现此接口完成进度显示 + * @param mode {@link Mode} 模式 + * @return this + * @since 5.7.16 + */ + public Sftp put(InputStream srcStream, String destPath, SftpProgressMonitor monitor, Mode mode) { + try { + channel.put(srcStream, destPath, monitor, mode.ordinal()); + } catch (SftpException e) { + throw new JschRuntimeException(e); + } + return this; + } + @Override public void download(String src, File destFile) { get(src, FileUtil.getAbsolutePath(destFile)); diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index cfb5dd251..7f31d796f 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.codec.Base64; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; +import com.google.zxing.BarcodeFormat; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.junit.Assert; import org.junit.Ignore; @@ -87,4 +88,10 @@ public class QrCodeUtilTest { final String decode = QrCodeUtil.decode(ImgUtil.read("d:/test/qr_a.png"), false, true); Console.log(decode); } + + @Test + public void pdf417Test(){ + final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.PDF_417, QrConfig.create()); + Assert.assertNotNull(image); + } } diff --git a/hutool-extra/src/test/resources/config/mail.setting b/hutool-extra/src/test/resources/config/mail.setting index a7aa08342..2fd907007 100644 --- a/hutool-extra/src/test/resources/config/mail.setting +++ b/hutool-extra/src/test/resources/config/mail.setting @@ -11,7 +11,7 @@ port = 465 # 发件人(必须正确,否则发送失败) from = 小磊 # 用户名(注意:如果使用foxmail邮箱,此处user为qq号) -user = hutool +user = hutool@yeah.net # 密码 pass = q1w2e3 # 使用 STARTTLS安全连接 @@ -20,3 +20,7 @@ starttlsEnable = true sslEnable = true # 调试模式 debug = true +# 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名) +splitlongparameters = false +# 是否编码附件文件名(默认true) +encodefilename = true diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index d8c8d1f83..b86a35819 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java index eda670ac5..f64f1ecd7 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -148,9 +148,9 @@ public class HttpResponse extends HttpBase implements Closeable { } /** - * 是否为zlib(Defalte)压缩过的内容 + * 是否为zlib(Deflate)压缩过的内容 * - * @return 是否为zlib(Defalte)压缩过的内容 + * @return 是否为zlib(Deflate)压缩过的内容 * @since 4.5.7 */ public boolean isDeflate() { diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 96e3feb61..7a94609b1 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-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 3cdea5a83..df5f34f13 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -4,7 +4,7 @@ import cn.hutool.core.bean.BeanPath; import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Filter; -import cn.hutool.core.lang.Pair; +import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.text.StrJoiner; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -446,13 +446,13 @@ public class JSONArray implements JSON, JSONGetter, List, Rando /** * 加入或者替换JSONArray中指定Index的值,如果index大于JSONArray的长度,将在指定index设置值,之前的位置填充JSONNull.Null * - * @param index 位置 + * @param index 位置 * @param element 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return 替换的值,即之前的值 */ @Override public Object set(int index, Object element) { - if(index >= size()){ + if (index >= size()) { add(index, element); } return this.rawList.set(index, JSONUtil.wrap(element, this.config)); @@ -537,11 +537,11 @@ public class JSONArray implements JSON, JSONGetter, List, Rando * 支持过滤器,即选择哪些字段或值不写出 * * @param indentFactor 每层缩进空格数 - * @param filter 键值对过滤器 + * @param filter 过滤器,可以修改值,key(index)无法修改 * @return JSON字符串 * @since 5.7.15 */ - public String toJSONString(int indentFactor, Filter> filter){ + public String toJSONString(int indentFactor, Filter> filter) { final StringWriter sw = new StringWriter(); synchronized (sw.getBuffer()) { return this.write(sw, indentFactor, 0, filter).toString(); @@ -560,18 +560,19 @@ public class JSONArray implements JSON, JSONGetter, List, Rando * @param writer writer * @param indentFactor 缩进因子,定义每一级别增加的缩进量 * @param indent 本级别缩进量 - * @param filter 过滤器 + * @param filter 过滤器,可以修改值,key(index)无法修改 * @return Writer * @throws JSONException JSON相关异常 * @since 5.7.15 */ - public Writer write(Writer writer, int indentFactor, int indent, Filter> filter) throws JSONException { + public Writer write(Writer writer, int indentFactor, int indent, Filter> filter) throws JSONException { final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginArray(); - CollUtil.forEach(this, (value, index)->{ - if (null == filter || filter.accept(new Pair<>(index, value))) { - jsonWriter.writeValue(value); + CollUtil.forEach(this, (value, index) -> { + final MutablePair pair = new MutablePair<>(index, value); + if (null == filter || filter.accept(pair)) { + jsonWriter.writeValue(pair.getValue()); } }); jsonWriter.end(); @@ -580,6 +581,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando } // ------------------------------------------------------------------------------------------------- Private method start + /** * 初始化 * 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 7358cedec..ca7c08613 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -7,7 +7,7 @@ import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Filter; -import cn.hutool.core.lang.Pair; +import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.map.MapUtil; @@ -560,11 +560,11 @@ public class JSONObject implements JSON, JSONGetter, Map * 支持过滤器,即选择哪些字段或值不写出 * * @param indentFactor 每层缩进空格数 - * @param filter 键值对过滤器 + * @param filter 过滤器,同时可以修改编辑键和值 * @return JSON字符串 * @since 5.7.15 */ - public String toJSONString(int indentFactor, Filter> filter){ + public String toJSONString(int indentFactor, Filter> filter) { final StringWriter sw = new StringWriter(); synchronized (sw.getBuffer()) { return this.write(sw, indentFactor, 0, filter).toString(); @@ -583,17 +583,18 @@ public class JSONObject implements JSON, JSONGetter, Map * @param writer writer * @param indentFactor 缩进因子,定义每一级别增加的缩进量 * @param indent 本级别缩进量 - * @param filter 过滤器 + * @param filter 过滤器,同时可以修改编辑键和值 * @return Writer * @throws JSONException JSON相关异常 * @since 5.7.15 */ - public Writer write(Writer writer, int indentFactor, int indent, Filter> filter) throws JSONException { + public Writer write(Writer writer, int indentFactor, int indent, Filter> filter) throws JSONException { final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - if (null == filter || filter.accept(new Pair<>(key, value))) { - jsonWriter.writeField(key, value); + final MutablePair pair = new MutablePair<>(key, value); + if (null == filter || filter.accept(pair)) { + jsonWriter.writeField(pair.getKey(), pair.getValue()); } }); jsonWriter.end(); 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 f2983b859..f3ae62388 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -10,6 +10,7 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.test.bean.JSONBean; import cn.hutool.json.test.bean.ResultDto; @@ -67,7 +68,7 @@ public class JSONObjectTest { @Test public void toStringTest3() { JSONObject json = Objects.requireNonNull(JSONUtil.createObj()// - .set("dateTime", DateUtil.parse("2019-05-02 22:12:01")))// + .set("dateTime", DateUtil.parse("2019-05-02 22:12:01")))// .setDateFormat(DatePattern.NORM_DATE_PATTERN); Assert.assertEquals("{\"dateTime\":\"2019-05-02\"}", json.toString()); } @@ -86,14 +87,14 @@ public class JSONObjectTest { @Test public void putAllTest() { JSONObject json1 = JSONUtil.createObj() - .set("a", "value1") - .set("b", "value2") - .set("c", "value3") - .set("d", true); + .set("a", "value1") + .set("b", "value2") + .set("c", "value3") + .set("d", true); JSONObject json2 = JSONUtil.createObj() - .set("a", "value21") - .set("b", "value22"); + .set("a", "value21") + .set("b", "value22"); // putAll操作会覆盖相同key的值,因此a,b两个key的值改变,c的值不变 json1.putAll(json2); @@ -399,7 +400,7 @@ public class JSONObjectTest { } @Test - public void aliasTest(){ + public void aliasTest() { final BeanWithAlias beanWithAlias = new BeanWithAlias(); beanWithAlias.setValue1("张三"); beanWithAlias.setValue2(35); @@ -417,7 +418,7 @@ public class JSONObjectTest { } @Test - public void setDateFormatTest(){ + public void setDateFormatTest() { JSONConfig jsonConfig = JSONConfig.create(); jsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); jsonConfig.setOrder(true); @@ -430,7 +431,7 @@ public class JSONObjectTest { } @Test - public void setDateFormatTest2(){ + public void setDateFormatTest2() { JSONConfig jsonConfig = JSONConfig.create(); jsonConfig.setDateFormat("yyyy#MM#dd"); jsonConfig.setOrder(true); @@ -451,7 +452,7 @@ public class JSONObjectTest { } @Test - public void setCustomDateFormatTest(){ + public void setCustomDateFormatTest() { JSONConfig jsonConfig = JSONConfig.create(); jsonConfig.setDateFormat("#sss"); jsonConfig.setOrder(true); @@ -472,7 +473,7 @@ public class JSONObjectTest { } @Test - public void getTimestampTest(){ + public void getTimestampTest() { String timeStr = "1970-01-01 00:00:00"; final JSONObject jsonObject = JSONUtil.createObj().set("time", timeStr); final Timestamp time = jsonObject.get("time", Timestamp.class); @@ -518,7 +519,7 @@ public class JSONObjectTest { } @Test - public void parseBeanSameNameTest(){ + public void parseBeanSameNameTest() { final SameNameBean sameNameBean = new SameNameBean(); final JSONObject parse = JSONUtil.parseObj(sameNameBean); Assert.assertEquals("123", parse.getStr("username")); @@ -537,9 +538,11 @@ public class JSONObjectTest { public static class SameNameBean { private final String username = "123"; private final String userName = "abc"; + public String getUsername() { return username; } + @PropIgnore private final String fieldToIgnore = "sfdsdads"; @@ -547,13 +550,13 @@ public class JSONObjectTest { return userName; } - public String getFieldToIgnore(){ + public String getFieldToIgnore() { return this.fieldToIgnore; } } @Test - public void setEntryTest(){ + public void setEntryTest() { final HashMap of = MapUtil.of("test", "testValue"); final Set> entries = of.entrySet(); final Map.Entry next = entries.iterator().next(); @@ -563,13 +566,13 @@ public class JSONObjectTest { } @Test(expected = JSONException.class) - public void createJSONObjectTest(){ + public void createJSONObjectTest() { // 集合类不支持转为JSONObject new JSONObject(new JSONArray(), JSONConfig.create()); } @Test - public void floatTest(){ + public void floatTest() { Map map = new HashMap<>(); map.put("c", 2.0F); @@ -578,7 +581,7 @@ public class JSONObjectTest { } @Test - public void accumulateTest(){ + public void accumulateTest() { final JSONObject jsonObject = JSONUtil.createObj().accumulate("key1", "value1"); Assert.assertEquals("{\"key1\":\"value1\"}", jsonObject.toString()); @@ -598,7 +601,7 @@ public class JSONObjectTest { @Test - public void bigDecimalTest(){ + public void bigDecimalTest() { String jsonStr = "{\"orderId\":\"1704747698891333662002277\"}"; BigDecimalBean bigDecimalBean = JSONUtil.toBean(jsonStr, BigDecimalBean.class); Assert.assertEquals("{\"orderId\":1704747698891333662002277}", JSONUtil.toJsonStr(bigDecimalBean)); @@ -606,12 +609,12 @@ public class JSONObjectTest { @Data static - class BigDecimalBean{ + class BigDecimalBean { private BigDecimal orderId; } @Test - public void filterIncludeTest(){ + public void filterIncludeTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) .set("a", "value1") .set("b", "value2") @@ -623,7 +626,7 @@ public class JSONObjectTest { } @Test - public void filterExcludeTest(){ + public void filterExcludeTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) .set("a", "value1") .set("b", "value2") @@ -633,4 +636,37 @@ public class JSONObjectTest { final String s = json1.toJSONString(0, (pair) -> false == pair.getKey().equals("b")); Assert.assertEquals("{\"a\":\"value1\",\"c\":\"value3\",\"d\":true}", s); } + + @Test + public void editTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("a", "value1") + .set("b", "value2") + .set("c", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + if ("b".equals(pair.getKey())) { + // 修改值为新值 + pair.setValue(pair.getValue() + "_edit"); + return true; + } + // 除了"b",其他都去掉 + return false; + }); + Assert.assertEquals("{\"b\":\"value2_edit\"}", s); + } + + @Test + public void nullToEmptyTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) + .set("a", null) + .set("b", "value2"); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setValue(ObjectUtil.defaultIfNull(pair.getValue(), StrUtil.EMPTY)); + return true; + }); + Assert.assertEquals("{\"a\":\"\",\"b\":\"value2\"}", s); + } } diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index d50aaa75f..efd206d24 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-jwt diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index ba5ea9f7e..f0175f355 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index e1cb315c7..90a2094c1 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java index 02663d60b..68f8d7c7f 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java @@ -736,7 +736,7 @@ public class ExcelWriter extends ExcelBase { CellStyle style = null; if (null != this.styleSet) { - style = (isSetHeaderStyle && null != this.styleSet.headCellStyle) ? this.styleSet.headCellStyle : this.styleSet.cellStyle; + style = styleSet.getStyleByValueType(content, isSetHeaderStyle); } return merge(firstRow, lastRow, firstColumn, lastColumn, content, style); 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 2bec87b58..6d94b4815 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 @@ -7,32 +7,48 @@ import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.Hyperlink; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; import java.io.Serializable; +import java.math.BigDecimal; +import java.time.temporal.TemporalAccessor; +import java.util.Calendar; +import java.util.Date; /** * 样式集合,此样式集合汇集了整个工作簿的样式,用于减少样式的创建和冗余 * * @author looly - * */ -public class StyleSet implements Serializable{ +public class StyleSet implements Serializable { private static final long serialVersionUID = 1L; - /** 工作簿引用 */ + /** + * 工作簿引用 + */ private final Workbook workbook; - /** 标题样式 */ + /** + * 标题样式 + */ protected CellStyle headCellStyle; - /** 默认样式 */ + /** + * 默认样式 + */ protected CellStyle cellStyle; - /** 默认数字样式 */ + /** + * 默认数字样式 + */ protected CellStyle cellStyleForNumber; - /** 默认日期样式 */ + /** + * 默认日期样式 + */ protected CellStyle cellStyleForDate; - /** 默认链接样式 */ + /** + * 默认链接样式 + */ protected CellStyle cellStyleForHyperlink; /** @@ -148,7 +164,7 @@ public class StyleSet implements Serializable{ * 设置单元格背景样式 * * @param backgroundColor 背景色 - * @param withHeadCell 是否也定义头部样式 + * @param withHeadCell 是否也定义头部样式 * @return this * @since 4.0.0 */ @@ -166,9 +182,9 @@ public class StyleSet implements Serializable{ /** * 设置全局字体 * - * @param color 字体颜色 - * @param fontSize 字体大小,-1表示默认大小 - * @param fontName 字体名,null表示默认字体 + * @param color 字体颜色 + * @param fontSize 字体大小,-1表示默认大小 + * @param fontName 字体名,null表示默认字体 * @param ignoreHead 是否跳过头部样式 * @return this */ @@ -180,7 +196,7 @@ public class StyleSet implements Serializable{ /** * 设置全局字体 * - * @param font 字体,可以通过{@link StyleUtil#createFont(Workbook, short, short, String)}创建 + * @param font 字体,可以通过{@link StyleUtil#createFont(Workbook, short, short, String)}创建 * @param ignoreHead 是否跳过头部样式 * @return this * @since 4.1.0 @@ -210,4 +226,44 @@ public class StyleSet implements Serializable{ return this; } + /** + * 获取值对应的公共单元格样式 + * + * @param value 值 + * @param isHeader 是否为标题单元格 + * @return 值对应单元格样式 + * @since 5.7.16 + */ + public CellStyle getStyleByValueType(Object value, boolean isHeader) { + CellStyle style = null; + + if (isHeader && null != this.headCellStyle) { + style = headCellStyle; + } else if (null != cellStyle) { + style = cellStyle; + } + + if (value instanceof Date + || value instanceof TemporalAccessor + || value instanceof Calendar) { + // 日期单独定义格式 + if (null != this.cellStyleForDate) { + style = this.cellStyleForDate; + } + } else if (value instanceof Number) { + // 数字单独定义格式 + if ((value instanceof Double || value instanceof Float || value instanceof BigDecimal) && + null != this.cellStyleForNumber) { + style = this.cellStyleForNumber; + } + } else if (value instanceof Hyperlink) { + // 自定义超链接样式 + if (null != this.cellStyleForHyperlink) { + style = this.cellStyleForHyperlink; + } + } + + return style; + } + } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java index 9b31c340d..2d3883ddb 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java @@ -15,7 +15,6 @@ import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.Comment; import org.apache.poi.ss.usermodel.CreationHelper; import org.apache.poi.ss.usermodel.Drawing; -import org.apache.poi.ss.usermodel.Hyperlink; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; @@ -23,11 +22,6 @@ import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.RegionUtil; import org.apache.poi.ss.util.SheetUtil; -import java.math.BigDecimal; -import java.time.temporal.TemporalAccessor; -import java.util.Calendar; -import java.util.Date; - /** * Excel表格中单元格工具类 * @@ -151,32 +145,7 @@ public class CellUtil { } if (null != styleSet) { - final CellStyle headCellStyle = styleSet.getHeadCellStyle(); - final CellStyle cellStyle = styleSet.getCellStyle(); - if (isHeader && null != headCellStyle) { - cell.setCellStyle(headCellStyle); - } else if (null != cellStyle) { - cell.setCellStyle(cellStyle); - } - } - - if (value instanceof Date - || value instanceof TemporalAccessor - || value instanceof Calendar) { - // 日期单独定义格式 - if (null != styleSet && null != styleSet.getCellStyleForDate()) { - cell.setCellStyle(styleSet.getCellStyleForDate()); - } - } else if (value instanceof Number) { - // 数字单独定义格式 - if ((value instanceof Double || value instanceof Float || value instanceof BigDecimal) && null != styleSet && null != styleSet.getCellStyleForNumber()) { - cell.setCellStyle(styleSet.getCellStyleForNumber()); - } - } else if(value instanceof Hyperlink){ - // 自定义超链接样式 - if (null != styleSet && null != styleSet.getCellStyleForHyperlink()) { - cell.setCellStyle(styleSet.getCellStyleForHyperlink()); - } + cell.setCellStyle(styleSet.getStyleByValueType(value, isHeader)); } setCellValue(cell, value); diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java index d33fc890b..0dc63eade 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java @@ -727,6 +727,19 @@ public class ExcelWriteTest { writer.close(); } + @Test + @Ignore + public void mergeForDateTest(){ + // https://github.com/dromara/hutool/issues/1911 + + //通过工具类创建writer + String path = "d:/test/mergeForDate.xlsx"; + FileUtil.del(path); + ExcelWriter writer = ExcelUtil.getWriter(path); + writer.merge(0, 3, 0, 2, DateUtil.date(), false); + writer.close(); + } + @Test @Ignore public void changeHeaderStyleTest(){ diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 56c4429c6..9661de356 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 81491727d..f70bafd70 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-setting diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java index c5186307f..81e349c2a 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java @@ -8,6 +8,7 @@ import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.SystemPropsUtil; import cn.hutool.log.Log; import java.io.BufferedReader; @@ -24,7 +25,7 @@ import java.util.Set; /** * Setting文件加载器 - * + * * @author Looly * */ @@ -47,7 +48,7 @@ public class SettingLoader { /** * 构造 - * + * * @param groupedMap GroupedMap */ public SettingLoader(GroupedMap groupedMap) { @@ -56,7 +57,7 @@ public class SettingLoader { /** * 构造 - * + * * @param groupedMap GroupedMap * @param charset 编码 * @param isUseVariable 是否使用变量 @@ -69,7 +70,7 @@ public class SettingLoader { /** * 加载设置文件 - * + * * @param resource 配置文件URL * @return 加载是否成功 */ @@ -93,7 +94,7 @@ public class SettingLoader { /** * 加载设置文件。 此方法不会关闭流对象 - * + * * @param settingStream 文件流 * @return 加载成功与否 * @throws IOException IO异常 @@ -146,7 +147,7 @@ public class SettingLoader { /** * 设置变量的正则
* 正则只能有一个group表示变量本身,剩余为字符 例如 \$\{(name)\}表示${name}变量名为name的一个变量表示 - * + * * @param regex 正则 */ public void setVarRegex(String regex) { @@ -155,7 +156,7 @@ public class SettingLoader { /** * 赋值分隔符(用于分隔键值对) - * + * * @param assignFlag 正则 * @since 4.6.5 */ @@ -166,7 +167,7 @@ public class SettingLoader { /** * 持久化当前设置,会覆盖掉之前的设置
* 持久化会不会保留之前的分组 - * + * * @param absolutePath 设置文件的绝对路径 */ public void store(String absolutePath) { @@ -194,7 +195,7 @@ public class SettingLoader { /** * 存储到Writer - * + * * @param writer Writer */ synchronized private void store(PrintWriter writer) { @@ -209,7 +210,7 @@ public class SettingLoader { // ----------------------------------------------------------------------------------- Private method start /** * 替换给定值中的变量标识 - * + * * @param group 所在分组 * @param value 值 * @return 替换后的字符串 @@ -230,13 +231,9 @@ public class SettingLoader { varValue = this.groupedMap.get(groupAndKey.get(0), groupAndKey.get(1)); } } - // 系统参数中查找 + // 系统参数和环境变量中查找 if (null == varValue) { - varValue = System.getProperty(key); - } - // 环境变量中查找 - if (null == varValue) { - varValue = System.getenv(key); + varValue = SystemPropsUtil.get(key); } if (null != varValue) { diff --git a/hutool-setting/src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java b/hutool-setting/src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java index e4b81985b..137fde726 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/yaml/YamlUtilTest.java @@ -13,7 +13,7 @@ public class YamlUtilTest { @Test public void loadByPathTest() { - final Dict result = YamlUtil.loadByPath("test.yaml", Dict.class); + final Dict result = YamlUtil.loadByPath("test.yaml"); Assert.assertEquals("John", result.getStr("firstName")); diff --git a/hutool-setting/src/test/resources/test.yaml b/hutool-setting/src/test/resources/test.yaml index 35d8b23b7..518ffecf0 100644 --- a/hutool-setting/src/test/resources/test.yaml +++ b/hutool-setting/src/test/resources/test.yaml @@ -11,3 +11,4 @@ homeAddress: city: "City Y" state: "State Y" zip: 345657 + 123: 345 diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index afc15da80..cb37a29e1 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index f89f373b9..916d12017 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool-system diff --git a/hutool-system/src/main/java/cn/hutool/system/SystemUtil.java b/hutool-system/src/main/java/cn/hutool/system/SystemUtil.java index fe27b9b1e..6a561b727 100644 --- a/hutool-system/src/main/java/cn/hutool/system/SystemUtil.java +++ b/hutool-system/src/main/java/cn/hutool/system/SystemUtil.java @@ -1,9 +1,9 @@ package cn.hutool.system; import cn.hutool.core.convert.Convert; -import cn.hutool.core.lang.Console; import cn.hutool.core.lang.Singleton; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.SystemPropsUtil; import java.io.PrintWriter; import java.lang.management.ClassLoadingMXBean; @@ -17,7 +17,6 @@ import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; import java.lang.management.ThreadMXBean; import java.util.List; -import java.util.Properties; /** * Java的System类封装工具类。
@@ -25,7 +24,7 @@ import java.util.Properties; * * @author Looly */ -public class SystemUtil { +public class SystemUtil extends SystemPropsUtil { // ----- Java运行时环境信息 -----/ /** @@ -149,117 +148,6 @@ public class SystemUtil { */ public final static String USER_DIR = SystemPropsKeys.USER_DIR; - // ----------------------------------------------------------------------- Basic start - - /** - * 取得系统属性,如果因为Java安全的限制而失败,则将错误打在Log中,然后返回 defaultValue - * - * @param name 属性名 - * @param defaultValue 默认值 - * @return 属性值或defaultValue - * @see System#getProperty(String) - * @see System#getenv(String) - */ - public static String get(String name, String defaultValue) { - return StrUtil.nullToDefault(get(name, false), defaultValue); - } - - /** - * 取得系统属性,如果因为Java安全的限制而失败,则将错误打在Log中,然后返回 {@code null} - * - * @param name 属性名 - * @param quiet 安静模式,不将出错信息打在{@code System.err}中 - * @return 属性值或{@code null} - * @see System#getProperty(String) - * @see System#getenv(String) - */ - public static String get(String name, boolean quiet) { - String value = null; - try { - value = System.getProperty(name); - } catch (SecurityException e) { - if (false == quiet) { - Console.error("Caught a SecurityException reading the system property '{}'; " + - "the SystemUtil property value will default to null.", name); - } - } - - if (null == value) { - try { - value = System.getenv(name); - } catch (SecurityException e) { - if (false == quiet) { - Console.error("Caught a SecurityException reading the system env '{}'; " + - "the SystemUtil env value will default to null.", name); - } - } - } - - return value; - } - - /** - * 获得System属性 - * - * @param key 键 - * @return 属性值 - * @see System#getProperty(String) - * @see System#getenv(String) - */ - public static String get(String key) { - return get(key, null); - } - - /** - * 获得boolean类型值 - * - * @param key 键 - * @param defaultValue 默认值 - * @return 值 - */ - public static boolean getBoolean(String key, boolean defaultValue) { - String value = get(key); - if (value == null) { - return defaultValue; - } - - value = value.trim().toLowerCase(); - if (value.isEmpty()) { - return true; - } - - return Convert.toBool(value, defaultValue); - } - - /** - * 获得int类型值 - * - * @param key 键 - * @param defaultValue 默认值 - * @return 值 - */ - public static long getInt(String key, int defaultValue) { - return Convert.toInt(get(key), defaultValue); - } - - /** - * 获得long类型值 - * - * @param key 键 - * @param defaultValue 默认值 - * @return 值 - */ - public static long getLong(String key, long defaultValue) { - return Convert.toLong(get(key), defaultValue); - } - - /** - * @return 属性列表 - */ - public static Properties props() { - return System.getProperties(); - } - /** * 获取当前进程 PID * @@ -268,7 +156,6 @@ public class SystemUtil { public static long getCurrentPID() { return Long.parseLong(getRuntimeMXBean().getName().split("@")[0]); } - // ----------------------------------------------------------------------- Basic end /** * 返回Java虚拟机类加载系统相关属性 diff --git a/pom.xml b/pom.xml index 7ef421831..3ea2cdd16 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.15 + 5.7.16-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool