mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
commit
3869b08263
29
CHANGELOG.md
29
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)
|
||||
|
@ -142,18 +142,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐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.
|
||||
|
@ -142,20 +142,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐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平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -1 +1 @@
|
||||
5.7.15
|
||||
5.7.16
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.7.15'
|
||||
var version = '5.7.16'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -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) {
|
||||
|
188
hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java
Normal file
188
hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java
Normal file
@ -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)。<br>
|
||||
* 百分号编码可用于URI的编码,也可以用于"application/x-www-form-urlencoded"的MIME准备数据。
|
||||
*
|
||||
* <p>
|
||||
* 百分号编码会对 URI 中不允许出现的字符或者其他特殊情况的允许的字符进行编码,对于被编码的字符,最终会转为以百分号"%“开头,后面跟着两位16进制数值的形式。
|
||||
* 举个例子,空格符(SP)是不允许的字符,在 ASCII 码对应的二进制值是"00100000”,最终转为"%20"。
|
||||
* </p>
|
||||
* <p>
|
||||
* 对于不同场景应遵循不同规范:
|
||||
*
|
||||
* <ul>
|
||||
* <li>URI:遵循RFC 3986保留字规范</li>
|
||||
* <li>application/x-www-form-urlencoded,遵循W3C HTML Form content types规范,如空格须转+</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* [a-zA-Z0-9]默认不被编码
|
||||
*/
|
||||
public PercentCodec() {
|
||||
this(new BitSet(256));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param safeCharacters 安全字符,安全字符不被编码
|
||||
*/
|
||||
public PercentCodec(BitSet safeCharacters) {
|
||||
this.safeCharacters = safeCharacters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加安全字符<br>
|
||||
* 安全字符不被编码
|
||||
*
|
||||
* @param c 字符
|
||||
* @return this
|
||||
*/
|
||||
public PercentCodec addSafe(char c) {
|
||||
safeCharacters.set(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除安全字符<br>
|
||||
* 安全字符不被编码
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
@ -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<? super JavaFileObject> diagnosticListener) {
|
||||
return SYSTEM_COMPILER.getStandardFileManager(null, null, null);
|
||||
return SYSTEM_COMPILER.getStandardFileManager(diagnosticListener, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<Opt<?>> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected Opt<?> convertInternal(Object value) {
|
||||
return Opt.ofNullable(value);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算相差时长
|
||||
*
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 秒表封装<br>
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务信息
|
||||
* 获取任务信息,类似于:
|
||||
* <pre>
|
||||
* StopWatch '[id]': running time = [total] ns
|
||||
* </pre>
|
||||
*
|
||||
* @return 任务信息
|
||||
*/
|
||||
public String shortSummary() {
|
||||
return StrUtil.format("StopWatch '{}': running time = {} ns", this.id, this.totalTimeNanos);
|
||||
return shortSummary(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务信息,类似于:
|
||||
* <pre>
|
||||
* StopWatch '[id]': running time = [total] [unit]
|
||||
* </pre>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -840,6 +840,7 @@ public class Assert {
|
||||
/**
|
||||
* 检查值是否在指定范围内
|
||||
*
|
||||
* @param <X> 异常类型
|
||||
* @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 <X> 异常类型
|
||||
* @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 <X> 异常类型
|
||||
* @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
|
||||
*/
|
||||
|
@ -262,6 +262,25 @@ public class Opt<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 如果包裹里元素的值存在,就执行对应的操作集,并返回本身
|
||||
* 如果不存在,返回一个空的{@code Opt}
|
||||
*
|
||||
* <p>属于 {@link #ifPresent}的链式拓展
|
||||
* <p>属于 {@link #peek(Consumer)}的动态拓展
|
||||
*
|
||||
* @param actions 值存在时执行的操作,动态参数,可传入数组,当数组为一个空数组时并不会抛出 {@code NPE}
|
||||
* @return this
|
||||
* @throws NullPointerException 如果值存在,并且传入的操作集中的元素为 {@code null}
|
||||
* @author VampireAchao
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final Opt<T> peeks(Consumer<T>... actions) throws NullPointerException {
|
||||
// 第三个参数 (opts, opt) -> null其实并不会执行到该函数式接口所以直接返回了个null
|
||||
return Stream.of(actions).reduce(this, Opt<T>::peek, (opts, opt) -> null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果包裹里元素的值存在,就返回本身,如果不存在,则使用传入的操作执行后获得的 {@code Opt}
|
||||
*
|
||||
|
@ -16,17 +16,17 @@ import java.util.Objects;
|
||||
public class Pair<K, V> extends CloneSupport<Pair<K, V>> 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 <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return {@link Pair}
|
||||
* @return {@code Pair}
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public static <K, V> Pair<K, V> of(K key, V value) {
|
||||
|
@ -32,9 +32,11 @@ public interface RegexPool {
|
||||
*/
|
||||
String GROUP_VAR = "\\$(\\d+)";
|
||||
/**
|
||||
* IP v4
|
||||
* IP v4<br>
|
||||
* 采用分组方式便于解析地址的每一个段
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
||||
/**
|
||||
* 可变 <code>double</code> 类型
|
||||
* 可变 {@code double} 类型
|
||||
*
|
||||
* @see Double
|
||||
* @since 3.0.1
|
||||
@ -150,12 +150,12 @@ public class MutableDouble extends Number implements Comparable<MutableDouble>,
|
||||
* 相等需同时满足如下条件:
|
||||
* <ol>
|
||||
* <li>非空</li>
|
||||
* <li>类型为 {@link MutableDouble}</li>
|
||||
* <li>类型为 {@code MutableDouble}</li>
|
||||
* <li>值相等</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param obj 比对的对象
|
||||
* @return 相同返回<code>true</code>,否则 <code>false</code>
|
||||
* @return 相同返回<code>true</code>,否则 {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
@ -175,7 +175,7 @@ public class MutableDouble extends Number implements Comparable<MutableDouble>,
|
||||
/**
|
||||
* 比较
|
||||
*
|
||||
* @param other 其它 {@link MutableDouble} 对象
|
||||
* @param other 其它 {@code MutableDouble} 对象
|
||||
* @return x==y返回0,x<y返回-1,x>y返回1
|
||||
*/
|
||||
@Override
|
||||
|
@ -0,0 +1,57 @@
|
||||
package cn.hutool.core.lang.mutable;
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
|
||||
/**
|
||||
* 可变{@link Pair}实现,可以修改键和值
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public class MutablePair<K, V> extends Pair<K, V> implements Mutable<Pair<K, V>>{
|
||||
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<K, V> setKey(K key) {
|
||||
this.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置值
|
||||
*
|
||||
* @param value 新值
|
||||
* @return this
|
||||
*/
|
||||
public MutablePair<K, V> setValue(V value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<K, V> get() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Pair<K, V> pair) {
|
||||
this.key = pair.getKey();
|
||||
this.value = pair.getValue();
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
98
hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java
Normal file
98
hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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}形式。<br>
|
||||
* 对于{@code null}处理规则如下:
|
||||
* <ul>
|
||||
* <li>如果key为{@code null},则这个键值对忽略</li>
|
||||
* <li>如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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<CharSequence, CharSequence> 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;
|
||||
}
|
||||
|
@ -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<br>
|
||||
* 因此使用此方法归一为一种表示形式,默认按照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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 <T> 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> T firstNonNull(T... array) {
|
||||
return firstMatch(Objects::nonNull, array);
|
||||
return firstMatch(ObjectUtil::isNotNull, array);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -292,7 +292,7 @@ public class ObjectUtil {
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public static <T> 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 <T> 被检查对象为{@code null}返回默认值,否则返回自定义handle处理后的返回值
|
||||
* @return 处理后的返回值
|
||||
* @since 5.4.6
|
||||
*/
|
||||
public static <T> T defaultIfNull(Object source, Supplier<? extends T> 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());
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 系统属性工具<br>
|
||||
* 此工具用于读取系统属性或环境变量信息,封装包括:
|
||||
* <ul>
|
||||
* <li>{@link System#getProperty(String)}</li>
|
||||
* <li>{@link System#getenv(String)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
172
hutool-core/src/main/java/cn/hutool/core/util/W84Util.java
Normal file
172
hutool-core/src/main/java/cn/hutool/core/util/W84Util.java
Normal file
@ -0,0 +1,172 @@
|
||||
package cn.hutool.core.util;
|
||||
|
||||
/**
|
||||
* 坐标转换相关工具类<br>
|
||||
*
|
||||
* 坐标转换相关参考网址: 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};
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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中的新函数,直接照搬了过来
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<String, String> 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<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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(){
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
@ -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 密钥,如果为<code>null</code>生成随机密钥
|
||||
* @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},如果为<code>null</code>生成随机密钥
|
||||
* @param key 密钥{@link SecretKey},如果为{@code null}生成随机密钥
|
||||
* @return {@link HMac}
|
||||
* @since 3.0.3
|
||||
*/
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
@ -27,7 +27,7 @@
|
||||
<mail.version>1.6.2</mail.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<zxing.version>3.4.1</zxing.version>
|
||||
<net.version>3.7.2</net.version>
|
||||
<net.version>3.8.0</net.version>
|
||||
<emoji-java.version>5.1.1</emoji-java.version>
|
||||
<servlet-api.version>4.0.1</servlet-api.version>
|
||||
<spring-boot.version>2.5.6</spring-boot.version>
|
||||
|
@ -489,9 +489,9 @@ public class Ftp extends AbstractFtp {
|
||||
* 上传文件到指定目录,可选:
|
||||
*
|
||||
* <pre>
|
||||
* 1. path为null或""上传到当前路径
|
||||
* 2. path为相对路径则相对于当前路径的子路径
|
||||
* 3. path为绝对路径则上传到此路径
|
||||
* 1. destPath为null或""上传到当前路径
|
||||
* 2. destPath为相对路径则相对于当前路径的子路径
|
||||
* 3. destPath为绝对路径则上传到此路径
|
||||
* </pre>
|
||||
*
|
||||
* @param file 文件
|
||||
@ -512,9 +512,9 @@ public class Ftp extends AbstractFtp {
|
||||
* 上传文件到指定目录,可选:
|
||||
*
|
||||
* <pre>
|
||||
* 1. path为null或""上传到当前路径
|
||||
* 2. path为相对路径则相对于当前路径的子路径
|
||||
* 3. path为绝对路径则上传到此路径
|
||||
* 1. destPath为null或""上传到当前路径
|
||||
* 2. destPath为相对路径则相对于当前路径的子路径
|
||||
* 3. destPath为绝对路径则上传到此路径
|
||||
* </pre>
|
||||
*
|
||||
* @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径
|
||||
|
@ -17,11 +17,11 @@ import java.util.List;
|
||||
* @since 3.2.3
|
||||
*/
|
||||
public class InternalMailUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 将多个字符串邮件地址转为{@link InternetAddress}列表<br>
|
||||
* 单个字符串地址可以是多个地址合并的字符串
|
||||
*
|
||||
*
|
||||
* @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 {
|
||||
/**
|
||||
* 将一个地址字符串解析为多个地址<br>
|
||||
* 地址间使用" "、","、";"分隔
|
||||
*
|
||||
*
|
||||
* @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 {
|
||||
/**
|
||||
* 编码中文字符<br>
|
||||
* 编码失败返回原字符串
|
||||
*
|
||||
*
|
||||
* @param text 被编码的文本
|
||||
* @param charset 编码
|
||||
* @return 编码后的结果
|
||||
|
@ -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<MimeMessage> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 邮箱帐户信息以及一些客户端配置信息
|
||||
@ -262,7 +264,10 @@ public class Mail implements Builder<MimeMessage> {
|
||||
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<MimeMessage> {
|
||||
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<MimeMessage> {
|
||||
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<MimeMessage> {
|
||||
/**
|
||||
* 构建邮件信息主体
|
||||
*
|
||||
* @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<MimeMessage> {
|
||||
private Session getSession() {
|
||||
final Session session = MailUtil.getSession(this.mailAccount, this.useGlobalSession);
|
||||
|
||||
if(null != this.debugOutput){
|
||||
if (null != this.debugOutput) {
|
||||
session.setDebugOut(debugOutput);
|
||||
}
|
||||
|
||||
|
@ -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}并设置:
|
||||
* <pre>
|
||||
* System.setProperty("mail.mime.charset", charset);
|
||||
* </pre>
|
||||
*
|
||||
* @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(国内邮箱附件不支持切分的附件名)<br>
|
||||
* 注意此项为全局设置,此项会调用
|
||||
* <pre>
|
||||
* System.setProperty("mail.mime.splitlongparameters", true)
|
||||
* </pre>
|
||||
*
|
||||
* @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}编码,此选项不会修改全局配置<br>
|
||||
* 如果此选项设置为{@code false},则是否编码取决于两个系统属性:
|
||||
* <ul>
|
||||
* <li>mail.mime.encodefilename 是否编码附件文件名</li>
|
||||
* <li>mail.mime.charset 编码文件名的编码</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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) {
|
||||
// 如果密码非空白,则使用认证模式
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<EncodeHintType, Object> toHints() {
|
||||
return toHints(BarcodeFormat.QR_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为Zxing的二维码配置
|
||||
*
|
||||
* @param format 格式,根据格式不同,{@link #errorCorrection}的值类型有所不同
|
||||
* @return 配置
|
||||
*/
|
||||
public HashMap<EncodeHintType, Object> toHints(BarcodeFormat format) {
|
||||
// 配置
|
||||
final HashMap<EncodeHintType, Object> 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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到指定目录,可选:
|
||||
*
|
||||
* <pre>
|
||||
* 1. path为null或""上传到当前路径
|
||||
* 2. path为相对路径则相对于当前路径的子路径
|
||||
* 3. path为绝对路径则上传到此路径
|
||||
* </pre>
|
||||
*
|
||||
* @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));
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ port = 465
|
||||
# 发件人(必须正确,否则发送失败)
|
||||
from = 小磊<hutool@yeah.net>
|
||||
# 用户名(注意:如果使用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
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -148,9 +148,9 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为zlib(Defalte)压缩过的内容
|
||||
* 是否为zlib(Deflate)压缩过的内容
|
||||
*
|
||||
* @return 是否为zlib(Defalte)压缩过的内容
|
||||
* @return 是否为zlib(Deflate)压缩过的内容
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public boolean isDeflate() {
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
@ -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<Integer>, List<Object>, 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<Integer>, List<Object>, Rando
|
||||
* 支持过滤器,即选择哪些字段或值不写出
|
||||
*
|
||||
* @param indentFactor 每层缩进空格数
|
||||
* @param filter 键值对过滤器
|
||||
* @param filter 过滤器,可以修改值,key(index)无法修改
|
||||
* @return JSON字符串
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public String toJSONString(int indentFactor, Filter<Pair<Integer, Object>> filter){
|
||||
public String toJSONString(int indentFactor, Filter<MutablePair<Integer, Object>> 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<Integer>, List<Object>, 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<Pair<Integer, Object>> filter) throws JSONException {
|
||||
public Writer write(Writer writer, int indentFactor, int indent, Filter<MutablePair<Integer, Object>> 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<Integer, Object> 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<Integer>, List<Object>, Rando
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*
|
||||
|
@ -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<String>, Map<String, Object>
|
||||
* 支持过滤器,即选择哪些字段或值不写出
|
||||
*
|
||||
* @param indentFactor 每层缩进空格数
|
||||
* @param filter 键值对过滤器
|
||||
* @param filter 过滤器,同时可以修改编辑键和值
|
||||
* @return JSON字符串
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public String toJSONString(int indentFactor, Filter<Pair<String, Object>> filter){
|
||||
public String toJSONString(int indentFactor, Filter<MutablePair<String, Object>> 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<String>, Map<String, Object>
|
||||
* @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<Pair<String, Object>> filter) throws JSONException {
|
||||
public Writer write(Writer writer, int indentFactor, int indent, Filter<MutablePair<String, Object>> 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<String, Object> pair = new MutablePair<>(key, value);
|
||||
if (null == filter || filter.accept(pair)) {
|
||||
jsonWriter.writeField(pair.getKey(), pair.getValue());
|
||||
}
|
||||
});
|
||||
jsonWriter.end();
|
||||
|
@ -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<String, String> of = MapUtil.of("test", "testValue");
|
||||
final Set<Map.Entry<String, String>> entries = of.entrySet();
|
||||
final Map.Entry<String, String> 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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-jwt</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-log</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
|
@ -736,7 +736,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter> {
|
||||
|
||||
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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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(){
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-script</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
@ -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 {
|
||||
/**
|
||||
* 设置变量的正则<br>
|
||||
* 正则只能有一个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 {
|
||||
/**
|
||||
* 持久化当前设置,会覆盖掉之前的设置<br>
|
||||
* 持久化会不会保留之前的分组
|
||||
*
|
||||
*
|
||||
* @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) {
|
||||
|
@ -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"));
|
||||
|
||||
|
@ -11,3 +11,4 @@ homeAddress:
|
||||
city: "City Y"
|
||||
state: "State Y"
|
||||
zip: 345657
|
||||
123: 345
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-system</artifactId>
|
||||
|
@ -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类封装工具类。<br>
|
||||
@ -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虚拟机类加载系统相关属性
|
||||
|
2
pom.xml
2
pom.xml
@ -8,7 +8,7 @@
|
||||
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.15</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
<name>hutool</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/dromara/hutool</url>
|
||||
|
Loading…
x
Reference in New Issue
Block a user