mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge remote-tracking branch 'upstream/v5-dev' into v5-dev
This commit is contained in:
commit
2ea7480527
53
CHANGELOG.md
53
CHANGELOG.md
@ -3,7 +3,52 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.5 (2020-12-24)
|
||||
# 5.5.8 (2021-01-09)
|
||||
|
||||
### 新特性
|
||||
### Bug修复
|
||||
* 【core 】 修复FileUtil.move以及PathUtil.copy等无法自动创建父目录的问题(issue#I2CKTI@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.7 (2021-01-07)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 DynaBean.create增加重载方法(pr#245@Gitee)
|
||||
* 【core 】 IdcardUtil增加重载是否忽略大小写(issue#1348@Github)
|
||||
* 【poi 】 SheetRidReader增加getRidByIndex方法(issue#1342@Github)
|
||||
* 【extra 】 MailAccount增加sslProtocols配置项(issue#IZN95@Gitee)
|
||||
* 【extra 】 MailUtil增加getSession方法
|
||||
* 【setting】 新增setByGroup和putByGroup,set和put标记为过期(issue#I2C42H@Gitee)
|
||||
* 【crypto 】 修改SymmetricAlgorithm注释(issue#1360@Github)
|
||||
* 【all 】 pom中将META-INF/maven下全部exclude(pr#1355@Github)
|
||||
* 【http 】 SimpleServer中增加addFilter等方法,并使用全局线程池
|
||||
* 【core 】 CollUtil.forEach 增加null 判断(pr#250@Gitee)
|
||||
* 【extra 】 FtpConfig增加serverLanguageCode和systemKey配置,Ftp.download增加重载(pr#248@Gitee)
|
||||
|
||||
### Bug修复
|
||||
* 【core 】 修复CsvReader读取双引号未转义问题(issue#I2BMP1@Gitee)
|
||||
* 【json 】 JSONUtil.parse修复config无效问题(issue#1363@Github)
|
||||
* 【http 】 修复SimpleServer返回响应内容Content-Length不正确的问题(issue#1358@Github)
|
||||
* 【http 】 修复Https请求部分环境下报证书验证异常问题(issue#I2C1BZ@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.6 (2020-12-29)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 手机号工具类 座机正则表达式统一管理(pr#243@Gitee)
|
||||
* 【extra 】 Mail增加setDebugOutput方法(issue#1335@Gitee)
|
||||
|
||||
### Bug修复
|
||||
* 【core 】 修复ZipUtil.unzip从流解压关闭问题(issue#I2B0S1@Gitee)
|
||||
* 【poi 】 修复Excel07Writer写出表格错乱问题(issue#I2B57B@Gitee)
|
||||
* 【poi 】 修复SheetRidReader读取字段错误问题(issue#1342@Github)
|
||||
* 【core 】 修复FileUtil.getMimeType不支持css和js(issue#1341@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.5 (2020-12-27)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 URLUtil.normalize新增重载(pr#233@Gitee)
|
||||
@ -13,14 +58,20 @@
|
||||
* 【poi 】 增加ExcelDateUtil更多日期格式支持(issue#1316@Github)
|
||||
* 【core 】 NumberUtil.toBigDecimal支持各类数字格式,如1,234.56等(issue#1334@Github)
|
||||
* 【core 】 NumberUtil增加parseXXX方法(issue#1334@Github)
|
||||
* 【poi 】 Excel07SaxReader支持通过sheetName读取(issue#I2AOSE@Gitee)
|
||||
|
||||
### Bug修复
|
||||
* 【core 】 FileUtil.isSub相对路径判断问题(pr#1315@Github)
|
||||
* 【core 】 TreeUtil增加空判定(issue#I2ACCW@Gitee)
|
||||
* 【db 】 解决Hive获取表名失败问题(issue#I2AGLU@Gitee)
|
||||
* 【core 】 修复DateUtil.parse未使用严格模式导致结果不正常的问题(issue#1332@Github)
|
||||
* 【core 】 修复RuntimeUtil.getUsableMemory非static问题(issue#I2AQ2M@Gitee)
|
||||
* 【core 】 修复ArrayUtil.equals方法严格判断问题(issue#I2AO8B@Gitee)
|
||||
* 【poi 】 修复SheetRidReader在获取rid时读取错误问题(issue#I2AOQW@Gitee)
|
||||
* 【core 】 修复强依赖了POI的问题(issue#1336@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.4 (2020-12-16)
|
||||
|
||||
### 新特性
|
||||
|
@ -125,19 +125,19 @@ Each module can be introduced individually, or all modules can be introduced by
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.5.5</version>
|
||||
<version>5.5.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.5.5'
|
||||
compile 'cn.hutool:hutool-all:5.5.8'
|
||||
```
|
||||
|
||||
## Download
|
||||
|
||||
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.5/)
|
||||
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.5/)
|
||||
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.8/)
|
||||
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.8/)
|
||||
|
||||
> 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.
|
||||
|
@ -123,21 +123,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.5.5</version>
|
||||
<version>5.5.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.5.5'
|
||||
compile 'cn.hutool:hutool-all:5.5.8'
|
||||
```
|
||||
|
||||
### 非Maven项目
|
||||
|
||||
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.5/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.5/)
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.8/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.8/)
|
||||
|
||||
> 注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -1 +1 @@
|
||||
5.5.5
|
||||
5.5.8
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.5.5'
|
||||
var version = '5.5.8'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
@ -130,6 +130,14 @@
|
||||
<include>${project.groupId}:*:*</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/maven/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -5,19 +5,11 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi</artifactId>
|
||||
<version>4.1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -22,21 +22,32 @@ public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
|
||||
private final Object bean;
|
||||
|
||||
/**
|
||||
* 创建一个{@link DynaBean}
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param bean 普通Bean
|
||||
* @return {@link DynaBean}
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Object bean) {
|
||||
return new DynaBean(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个{@link DynaBean}
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Class<?> beanClass) {
|
||||
return new DynaBean(beanClass);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建一个DynaBean
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
* @param params 构造Bean所需要的参数
|
||||
* @return {@link DynaBean}
|
||||
* @return DynaBean
|
||||
*/
|
||||
public static DynaBean create(Class<?> beanClass, Object... params) {
|
||||
return new DynaBean(beanClass, params);
|
||||
@ -54,6 +65,15 @@ public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
|
||||
this(ReflectUtil.newInstance(beanClass, params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param beanClass Bean类
|
||||
*/
|
||||
public DynaBean(Class<?> beanClass) {
|
||||
this(ReflectUtil.newInstance(beanClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
@ -83,7 +103,7 @@ public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
|
||||
return (T) ((Map<?, ?>) bean).get(fieldName);
|
||||
} else {
|
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
if(null == prop){
|
||||
if (null == prop) {
|
||||
throw new BeanException("No public field or get method for {}", fieldName);
|
||||
}
|
||||
return (T) prop.getValue(bean);
|
||||
@ -97,7 +117,7 @@ public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
|
||||
* @return 是否有bean属性
|
||||
* @since 5.4.2
|
||||
*/
|
||||
public boolean containsProp(String fieldName){
|
||||
public boolean containsProp(String fieldName) {
|
||||
return null != BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
}
|
||||
|
||||
@ -130,7 +150,7 @@ public class DynaBean extends CloneSupport<DynaBean> implements Serializable {
|
||||
((Map) bean).put(fieldName, value);
|
||||
} else {
|
||||
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
|
||||
if(null == prop){
|
||||
if (null == prop) {
|
||||
throw new BeanException("No public field or set method for {}", fieldName);
|
||||
}
|
||||
prop.setValue(bean, value);
|
||||
|
@ -2560,6 +2560,9 @@ public class CollUtil {
|
||||
* @since 5.4.7
|
||||
*/
|
||||
public static <T> void forEach(Iterable<T> iterable, Consumer<T> consumer) {
|
||||
if(iterable == null){
|
||||
return;
|
||||
}
|
||||
forEach(iterable.iterator(), consumer);
|
||||
}
|
||||
|
||||
@ -2571,6 +2574,9 @@ public class CollUtil {
|
||||
* @param consumer {@link Consumer} 遍历的每条数据处理器
|
||||
*/
|
||||
public static <T> void forEach(Iterator<T> iterator, Consumer<T> consumer) {
|
||||
if(iterator == null){
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
while (iterator.hasNext()) {
|
||||
consumer.accept(iterator.next(), index);
|
||||
@ -2586,6 +2592,9 @@ public class CollUtil {
|
||||
* @param consumer {@link Consumer} 遍历的每条数据处理器
|
||||
*/
|
||||
public static <T> void forEach(Enumeration<T> enumeration, Consumer<T> consumer) {
|
||||
if(enumeration == null){
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
while (enumeration.hasMoreElements()) {
|
||||
consumer.accept(enumeration.nextElement(), index);
|
||||
@ -2603,6 +2612,9 @@ public class CollUtil {
|
||||
* @param kvConsumer {@link KVConsumer} 遍历的每条数据处理器
|
||||
*/
|
||||
public static <K, V> void forEach(Map<K, V> map, KVConsumer<K, V> kvConsumer) {
|
||||
if(map == null){
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
for (Entry<K, V> entry : map.entrySet()) {
|
||||
kvConsumer.accept(entry.getKey(), entry.getValue(), index);
|
||||
|
@ -1,11 +1,12 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* {@link ByteBuffer} 工具类<br>
|
||||
* 此工具来自于 t-io 项目以及其它项目的相关部分收集<br>
|
||||
@ -13,7 +14,6 @@ import cn.hutool.core.util.StrUtil;
|
||||
*
|
||||
* @author tanyaowu, looly
|
||||
* @since 4.0.0
|
||||
*
|
||||
*/
|
||||
public class BufferUtil {
|
||||
|
||||
@ -248,4 +248,15 @@ public class BufferUtil {
|
||||
public static ByteBuffer createUtf8(CharSequence data) {
|
||||
return create(StrUtil.utf8Bytes(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建{@link CharBuffer}
|
||||
*
|
||||
* @param capacity 容量
|
||||
* @return {@link CharBuffer}
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public static CharBuffer createCharBuffer(int capacity) {
|
||||
return CharBuffer.allocate(capacity);
|
||||
}
|
||||
}
|
||||
|
@ -638,12 +638,10 @@ public class FileUtil extends PathUtil {
|
||||
* @return 父目录
|
||||
*/
|
||||
public static File mkParentDirs(File file) {
|
||||
final File parentFile = file.getParentFile();
|
||||
if (null != parentFile && false == parentFile.exists()) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
parentFile.mkdirs();
|
||||
if(null == file){
|
||||
return null;
|
||||
}
|
||||
return parentFile;
|
||||
return mkdir(file.getParentFile());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1022,6 +1020,7 @@ public class FileUtil extends PathUtil {
|
||||
* @param isOverride 是否覆盖目标文件
|
||||
* @return 目标文件
|
||||
* @since 3.0.9
|
||||
* @see PathUtil#rename(Path, String, boolean)
|
||||
*/
|
||||
public static File rename(File file, String newName, boolean isRetainExt, boolean isOverride) {
|
||||
if (isRetainExt) {
|
||||
@ -2914,7 +2913,21 @@ public class FileUtil extends PathUtil {
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static File writeFromStream(InputStream in, File dest) throws IORuntimeException {
|
||||
return FileWriter.create(dest).writeFromStream(in);
|
||||
return writeFromStream(in, dest, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将流的内容写入文件
|
||||
*
|
||||
* @param dest 目标文件
|
||||
* @param in 输入流
|
||||
* @param isCloseIn 是否关闭输入流
|
||||
* @return dest
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.5.6
|
||||
*/
|
||||
public static File writeFromStream(InputStream in, File dest, boolean isCloseIn) throws IORuntimeException {
|
||||
return FileWriter.create(dest).writeFromStream(in, isCloseIn);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3169,7 +3182,22 @@ public class FileUtil extends PathUtil {
|
||||
* @since 4.1.15
|
||||
*/
|
||||
public static String getMimeType(String filePath) {
|
||||
return URLConnection.getFileNameMap().getContentTypeFor(filePath);
|
||||
String contentType = URLConnection.getFileNameMap().getContentTypeFor(filePath);
|
||||
if(null == contentType){
|
||||
// 补充一些常用的mimeType
|
||||
if(filePath.endsWith(".css")){
|
||||
contentType = "text/css";
|
||||
} else if(filePath.endsWith(".js")){
|
||||
contentType = "application/x-javascript";
|
||||
}
|
||||
}
|
||||
|
||||
// 补充
|
||||
if(null == contentType){
|
||||
contentType = getMimeType(Paths.get(filePath));
|
||||
}
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -153,7 +153,8 @@ public class PathUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件
|
||||
* 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件<br>
|
||||
* 此方法不支持递归拷贝目录,如果src传入是目录,只会在目标目录中创建空目录
|
||||
*
|
||||
* @param src 源文件路径,如果为目录只在目标中创建新目录
|
||||
* @param dest 目标文件或目录,如果为目录使用与源文件相同的文件名
|
||||
@ -166,7 +167,8 @@ public class PathUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件
|
||||
* 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件<br>
|
||||
* 此方法不支持递归拷贝目录,如果src传入是目录,只会在目标目录中创建空目录
|
||||
*
|
||||
* @param src 源文件路径,如果为目录只在目标中创建新目录
|
||||
* @param target 目标文件或目录,如果为目录使用与源文件相同的文件名
|
||||
@ -180,6 +182,8 @@ public class PathUtil {
|
||||
Assert.notNull(target, "Destination File or directiory is null !");
|
||||
|
||||
final Path targetPath = isDirectory(target) ? target.resolve(src.getFileName()) : target;
|
||||
// 创建级联父目录
|
||||
mkParentDirs(targetPath);
|
||||
try {
|
||||
return Files.copy(src, targetPath, options);
|
||||
} catch (IOException e) {
|
||||
@ -188,9 +192,15 @@ public class PathUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文件或目录
|
||||
* 拷贝文件或目录,拷贝规则为:
|
||||
*
|
||||
* @param src 源文件路径,如果为目录只在目标中创建新目录
|
||||
* <ul>
|
||||
* <li>源文件为目录,目标也为目录或不存在,则拷贝整个目录到目标目录下</li>
|
||||
* <li>源文件为文件,目标为目录或不存在,则拷贝文件到目标目录下</li>
|
||||
* <li>源文件为文件,目标也为文件,则在{@link StandardCopyOption#REPLACE_EXISTING}情况下覆盖之</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param src 源文件路径,如果为目录会在目标中创建新目录
|
||||
* @param target 目标文件或目录,如果为目录使用与源文件相同的文件名
|
||||
* @param options {@link StandardCopyOption}
|
||||
* @return Path
|
||||
@ -198,17 +208,21 @@ public class PathUtil {
|
||||
* @since 5.5.1
|
||||
*/
|
||||
public static Path copy(Path src, Path target, CopyOption... options) throws IORuntimeException {
|
||||
if (isFile(src, false)) {
|
||||
return copyFile(src, target, options);
|
||||
}
|
||||
if (isDirectory(src)) {
|
||||
return copyContent(src, target.resolve(src.getFileName()), options);
|
||||
}
|
||||
return copyFile(src, target, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝目录下的所有文件或目录到目标目录中
|
||||
* 拷贝目录下的所有文件或目录到目标目录中,此方法不支持文件对文件的拷贝。
|
||||
* <ul>
|
||||
* <li>源文件为目录,目标也为目录或不存在,则拷贝目录下所有文件和目录到目标目录下</li>
|
||||
* <li>源文件为文件,目标为目录或不存在,则拷贝文件到目标目录下</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param src 源文件路径,如果为目录只在目标中创建新目录
|
||||
* @param target 目标文件或目录,如果为目录使用与源文件相同的文件名
|
||||
* @param target 目标目录,如果为目录使用与源文件相同的文件名
|
||||
* @param options {@link StandardCopyOption}
|
||||
* @return Path
|
||||
* @throws IORuntimeException IO异常
|
||||
@ -450,6 +464,8 @@ public class PathUtil {
|
||||
if (isDirectory(target)) {
|
||||
target = target.resolve(src.getFileName());
|
||||
}
|
||||
// 自动创建目标的父目录
|
||||
mkParentDirs(target);
|
||||
try {
|
||||
return Files.move(src, target, options);
|
||||
} catch (IOException e) {
|
||||
@ -546,6 +562,7 @@ public class PathUtil {
|
||||
* @param file 文件
|
||||
* @return MimeType
|
||||
* @since 5.5.5
|
||||
* @see Files#probeContentType(Path)
|
||||
*/
|
||||
public static String getMimeType(Path file) {
|
||||
try {
|
||||
@ -554,4 +571,33 @@ public class PathUtil {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建所给目录及其父目录
|
||||
*
|
||||
* @param dir 目录
|
||||
* @return 目录
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public static Path mkdir(Path dir) {
|
||||
if (null != dir && false == exists(dir, false)) {
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建所给文件或目录的父目录
|
||||
*
|
||||
* @param path 文件或目录
|
||||
* @return 父目录
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public static Path mkParentDirs(Path path) {
|
||||
return mkdir(path.getParent());
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package cn.hutool.core.io.file.visitor;
|
||||
|
||||
import cn.hutool.core.io.file.PathUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
@ -9,17 +11,28 @@ import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
/**
|
||||
* 文件拷贝的FileVisitor实现,用于递归遍历拷贝目录
|
||||
* 文件拷贝的FileVisitor实现,用于递归遍历拷贝目录,此类非线程安全<br>
|
||||
* 此类在遍历源目录并复制过程中会自动创建目标目录中不存在的上级目录。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.1
|
||||
*/
|
||||
public class CopyVisitor extends SimpleFileVisitor<Path> {
|
||||
|
||||
final Path source;
|
||||
final Path target;
|
||||
private final Path source;
|
||||
private final Path target;
|
||||
private boolean isTargetCreated;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source 源Path
|
||||
* @param target 目标Path
|
||||
*/
|
||||
public CopyVisitor(Path source, Path target) {
|
||||
if(PathUtil.exists(target, false) && false == PathUtil.isDirectory(target)){
|
||||
throw new IllegalArgumentException("Target must be a directory");
|
||||
}
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
@ -27,11 +40,13 @@ public class CopyVisitor extends SimpleFileVisitor<Path> {
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
|
||||
throws IOException {
|
||||
initTarget();
|
||||
// 将当前目录相对于源路径转换为相对于目标路径
|
||||
final Path targetDir = target.resolve(source.relativize(dir));
|
||||
try {
|
||||
Files.copy(dir, targetDir);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
if (!Files.isDirectory(targetDir))
|
||||
if (false == Files.isDirectory(targetDir))
|
||||
throw e;
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
@ -40,7 +55,18 @@ public class CopyVisitor extends SimpleFileVisitor<Path> {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
|
||||
throws IOException {
|
||||
initTarget();
|
||||
Files.copy(file, target.resolve(source.relativize(file)));
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化目标文件或目录
|
||||
*/
|
||||
private void initTarget(){
|
||||
if(false == this.isTargetCreated){
|
||||
PathUtil.mkdir(this.target);
|
||||
this.isTargetCreated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,10 @@ public class PatternPool {
|
||||
* 移动电话
|
||||
*/
|
||||
public final static Pattern MOBILE = Pattern.compile("(?:0|86|\\+86)?1[3-9]\\d{9}");
|
||||
|
||||
/**
|
||||
* 座机号码
|
||||
*/
|
||||
public final static Pattern TEL = Pattern.compile("0\\d{2,3}-[1-9]\\d{6,7}");
|
||||
/**
|
||||
* 18位身份证号码
|
||||
*/
|
||||
|
@ -1,12 +1,13 @@
|
||||
package cn.hutool.http.ssl;
|
||||
package cn.hutool.core.net;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* 证书管理
|
||||
* 默认信任管理器,默认信任所有客户端和服务端证书
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public class DefaultTrustManager implements X509TrustManager {
|
||||
|
@ -10,6 +10,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.IDN;
|
||||
@ -76,6 +77,39 @@ public class NetUtil {
|
||||
return Ipv4Util.ipv4ToLong(strIP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将IPv6地址字符串转为大整数
|
||||
*
|
||||
* @param IPv6Str 字符串
|
||||
* @return 大整数, 如发生异常返回 null
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public static BigInteger ipv6ToBitInteger(String IPv6Str) {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(IPv6Str);
|
||||
if (address instanceof Inet6Address) {
|
||||
return new BigInteger(1, address.getAddress());
|
||||
}
|
||||
} catch (UnknownHostException ignore) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将大整数转换成ipv6字符串
|
||||
*
|
||||
* @param bigInteger 大整数
|
||||
* @return IPv6字符串, 如发生异常返回 null
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public static String bigIntegerToIPv6(BigInteger bigInteger) {
|
||||
try {
|
||||
return InetAddress.getByAddress(bigInteger.toByteArray()).toString().substring(1);
|
||||
} catch (UnknownHostException ignore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测本地端口可用性<br>
|
||||
* 来自org.springframework.util.SocketUtils
|
||||
@ -512,7 +546,7 @@ public class NetUtil {
|
||||
byte[] mac = null;
|
||||
try {
|
||||
final NetworkInterface networkInterface = NetworkInterface.getByInetAddress(inetAddress);
|
||||
if(null != networkInterface){
|
||||
if (null != networkInterface) {
|
||||
mac = networkInterface.getHardwareAddress();
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
@ -547,9 +581,9 @@ public class NetUtil {
|
||||
}
|
||||
|
||||
final InetAddress localhost = getLocalhost();
|
||||
if(null != localhost){
|
||||
if (null != localhost) {
|
||||
String name = localhost.getHostName();
|
||||
if(StrUtil.isEmpty(name)){
|
||||
if (StrUtil.isEmpty(name)) {
|
||||
name = localhost.getHostAddress();
|
||||
}
|
||||
localhostName = name;
|
||||
@ -738,7 +772,7 @@ public class NetUtil {
|
||||
* @since 5.3.2
|
||||
*/
|
||||
public static boolean isOpen(InetSocketAddress address, int timeout) {
|
||||
try (Socket sc = new Socket()){
|
||||
try (Socket sc = new Socket()) {
|
||||
sc.connect(address, timeout);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
@ -52,7 +52,7 @@ public class SSLContextBuilder {
|
||||
|
||||
private String protocol = TLS;
|
||||
private KeyManager[] keyManagers;
|
||||
private TrustManager[] trustManagers;
|
||||
private TrustManager[] trustManagers = {new DefaultTrustManager()};
|
||||
private SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
|
||||
|
@ -1,22 +1,30 @@
|
||||
package cn.hutool.core.text.csv;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* CSV基础配置项
|
||||
* CSV基础配置项,此配置项可用于读取和写出CSV,定义了包括字段分隔符、文本包装符等符号
|
||||
*
|
||||
* @author looly
|
||||
* @since 4.0.5
|
||||
*/
|
||||
public class CsvConfig implements Serializable{
|
||||
public class CsvConfig implements Serializable {
|
||||
private static final long serialVersionUID = -8069578249066158459L;
|
||||
|
||||
/** 字段分隔符,默认逗号',' */
|
||||
/**
|
||||
* 字段分隔符,默认逗号','
|
||||
*/
|
||||
protected char fieldSeparator = CharUtil.COMMA;
|
||||
/** 文本分隔符,文本包装符,默认双引号'"' */
|
||||
/**
|
||||
* 文本包装符,默认双引号'"'
|
||||
*/
|
||||
protected char textDelimiter = CharUtil.DOUBLE_QUOTES;
|
||||
/**
|
||||
* 注释符号,用于区分注释行,默认'#'
|
||||
*/
|
||||
protected char commentCharacter = '#';
|
||||
|
||||
/**
|
||||
* 设置字段分隔符,默认逗号','
|
||||
@ -35,4 +43,14 @@ public class CsvConfig implements Serializable{
|
||||
public void setTextDelimiter(char textDelimiter) {
|
||||
this.textDelimiter = textDelimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 注释符号,用于区分注释行
|
||||
*
|
||||
* @param commentCharacter 注释符号,用于区分注释行
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public void setCommentCharacter(char commentCharacter) {
|
||||
this.commentCharacter = commentCharacter;
|
||||
}
|
||||
}
|
||||
|
@ -31,29 +31,39 @@ public final class CsvParser implements Closeable, Serializable {
|
||||
private final Reader reader;
|
||||
private final CsvReadConfig config;
|
||||
|
||||
private final char[] buf = new char[IoUtil.DEFAULT_LARGE_BUFFER_SIZE];
|
||||
/** 当前位置 */
|
||||
private int bufPos;
|
||||
/** 读取一段后数据长度 */
|
||||
private int bufLen;
|
||||
/** 拷贝开始的位置,一般为上一行的结束位置 */
|
||||
private int copyStart;
|
||||
/** 前一个特殊分界字符 */
|
||||
private final Buffer buf = new Buffer(IoUtil.DEFAULT_LARGE_BUFFER_SIZE);
|
||||
/**
|
||||
* 前一个特殊分界字符
|
||||
*/
|
||||
private int preChar = -1;
|
||||
/** 是否在引号包装内 */
|
||||
/**
|
||||
* 是否在引号包装内
|
||||
*/
|
||||
private boolean inQuotes;
|
||||
/** 当前读取字段 */
|
||||
/**
|
||||
* 当前读取字段
|
||||
*/
|
||||
private final StrBuilder currentField = new StrBuilder(512);
|
||||
|
||||
/** 标题行 */
|
||||
/**
|
||||
* 标题行
|
||||
*/
|
||||
private CsvRow header;
|
||||
/** 当前行号 */
|
||||
/**
|
||||
* 当前行号
|
||||
*/
|
||||
private long lineNo;
|
||||
/** 第一行字段数,用于检查每行字段数是否一致 */
|
||||
/**
|
||||
* 第一行字段数,用于检查每行字段数是否一致
|
||||
*/
|
||||
private int firstLineFieldCount = -1;
|
||||
/** 最大字段数量 */
|
||||
/**
|
||||
* 最大字段数量,用于初始化行,减少扩容
|
||||
*/
|
||||
private int maxFieldCount;
|
||||
/** 是否读取结束 */
|
||||
/**
|
||||
* 是否读取结束
|
||||
*/
|
||||
private boolean finished;
|
||||
|
||||
/**
|
||||
@ -84,7 +94,7 @@ public final class CsvParser implements Closeable, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
*读取下一行数据
|
||||
* 读取下一行数据
|
||||
*
|
||||
* @return CsvRow
|
||||
* @throws IORuntimeException IO读取异常
|
||||
@ -97,7 +107,7 @@ public final class CsvParser implements Closeable, Serializable {
|
||||
startingLineNo = ++lineNo;
|
||||
currentFields = readLine();
|
||||
fieldCount = currentFields.size();
|
||||
if(fieldCount < 1){
|
||||
if (fieldCount < 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
@ -142,7 +152,7 @@ public final class CsvParser implements Closeable, Serializable {
|
||||
final Map<String, Integer> localHeaderMap = new LinkedHashMap<>(currentFields.size());
|
||||
for (int i = 0; i < currentFields.size(); i++) {
|
||||
final String field = currentFields.get(i);
|
||||
if (StrUtil.isNotEmpty(field) && false ==localHeaderMap.containsKey(field)) {
|
||||
if (StrUtil.isNotEmpty(field) && false == localHeaderMap.containsKey(field)) {
|
||||
localHeaderMap.put(field, i);
|
||||
}
|
||||
}
|
||||
@ -159,42 +169,55 @@ public final class CsvParser implements Closeable, Serializable {
|
||||
private List<String> readLine() throws IORuntimeException {
|
||||
final List<String> currentFields = new ArrayList<>(maxFieldCount > 0 ? maxFieldCount : DEFAULT_ROW_CAPACITY);
|
||||
|
||||
final StrBuilder localCurrentField = currentField;
|
||||
final char[] localBuf = this.buf;
|
||||
int localBufPos = bufPos;//当前位置
|
||||
int localPreChar = preChar;//前一个特殊分界字符
|
||||
int localCopyStart = copyStart;//拷贝起始位置
|
||||
final StrBuilder currentField = this.currentField;
|
||||
final Buffer buf = this.buf;
|
||||
int preChar = this.preChar;//前一个特殊分界字符
|
||||
int copyLen = 0; //拷贝长度
|
||||
boolean lineStart = true;
|
||||
boolean inComment = false;
|
||||
|
||||
while (true) {
|
||||
if (bufLen == localBufPos) {
|
||||
if (false == buf.hasRemaining()) {
|
||||
// 此Buffer读取结束,开始读取下一段
|
||||
|
||||
if (copyLen > 0) {
|
||||
localCurrentField.append(localBuf, localCopyStart, copyLen);
|
||||
buf.appendTo(currentField, copyLen);
|
||||
// 此处无需mark,read方法会重置mark
|
||||
}
|
||||
try {
|
||||
bufLen = reader.read(localBuf);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
if (bufLen < 0) {
|
||||
if (buf.read(this.reader) < 0) {
|
||||
// CSV读取结束
|
||||
finished = true;
|
||||
|
||||
if (localPreChar == config.fieldSeparator || localCurrentField.hasContent()) {
|
||||
if (currentField.hasContent() || preChar == config.fieldSeparator) {
|
||||
//剩余部分作为一个字段
|
||||
currentFields.add(StrUtil.unWrap(localCurrentField.toStringAndReset(), config.textDelimiter));
|
||||
addField(currentFields, currentField.toStringAndReset());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//重置
|
||||
localCopyStart = localBufPos = copyLen = 0;
|
||||
copyLen = 0;
|
||||
}
|
||||
|
||||
final char c = localBuf[localBufPos++];
|
||||
final char c = buf.get();
|
||||
|
||||
// 注释行标记
|
||||
if(lineStart){
|
||||
if(c == this.config.commentCharacter){
|
||||
inComment = true;
|
||||
}
|
||||
lineStart = false;
|
||||
}
|
||||
// 注释行处理
|
||||
if(inComment){
|
||||
if ((c == CharUtil.CR || c == CharUtil.LF) && preChar != CharUtil.CR) {
|
||||
// 注释行以换行符为结尾
|
||||
inComment = false;
|
||||
}
|
||||
// 跳过注释行中的任何字符
|
||||
buf.mark();
|
||||
preChar = c;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inQuotes) {
|
||||
//引号内,做为内容,直到引号结束
|
||||
@ -202,55 +225,60 @@ public final class CsvParser implements Closeable, Serializable {
|
||||
// End of quoted text
|
||||
inQuotes = false;
|
||||
} else {
|
||||
if ((c == CharUtil.CR || c == CharUtil.LF) && localPreChar != CharUtil.CR) {
|
||||
// 新行
|
||||
if ((c == CharUtil.CR || c == CharUtil.LF) && preChar != CharUtil.CR) {
|
||||
lineNo++;
|
||||
}
|
||||
}
|
||||
// 普通字段字符
|
||||
copyLen++;
|
||||
} else {
|
||||
// 非引号内
|
||||
if (c == config.fieldSeparator) {
|
||||
//一个字段结束
|
||||
if (copyLen > 0) {
|
||||
localCurrentField.append(localBuf, localCopyStart, copyLen);
|
||||
buf.appendTo(currentField, copyLen);
|
||||
copyLen = 0;
|
||||
}
|
||||
currentFields.add(StrUtil.unWrap(localCurrentField.toStringAndReset(), config.textDelimiter));
|
||||
localCopyStart = localBufPos;
|
||||
buf.mark();
|
||||
addField(currentFields, currentField.toStringAndReset());
|
||||
} else if (c == config.textDelimiter) {
|
||||
// 引号开始
|
||||
inQuotes = true;
|
||||
copyLen++;
|
||||
} else if (c == CharUtil.CR) {
|
||||
// \r,直接结束
|
||||
if (copyLen > 0) {
|
||||
localCurrentField.append(localBuf, localCopyStart, copyLen);
|
||||
buf.appendTo(currentField, copyLen);
|
||||
}
|
||||
currentFields.add(StrUtil.unWrap(localCurrentField.toStringAndReset(), config.textDelimiter));
|
||||
localPreChar = c;
|
||||
localCopyStart = localBufPos;
|
||||
buf.mark();
|
||||
addField(currentFields, currentField.toStringAndReset());
|
||||
preChar = c;
|
||||
break;
|
||||
} else if (c == CharUtil.LF) {
|
||||
if (localPreChar != CharUtil.CR) {
|
||||
// \n
|
||||
if (preChar != CharUtil.CR) {
|
||||
if (copyLen > 0) {
|
||||
localCurrentField.append(localBuf, localCopyStart, copyLen);
|
||||
buf.appendTo(currentField, copyLen);
|
||||
}
|
||||
currentFields.add(StrUtil.unWrap(localCurrentField.toStringAndReset(), config.textDelimiter));
|
||||
localPreChar = c;
|
||||
localCopyStart = localBufPos;
|
||||
buf.mark();
|
||||
addField(currentFields, currentField.toStringAndReset());
|
||||
preChar = c;
|
||||
break;
|
||||
}
|
||||
localCopyStart = localBufPos;
|
||||
// 前一个字符是\r,已经处理过这个字段了,此处直接跳过
|
||||
buf.mark();
|
||||
} else {
|
||||
// 普通字符
|
||||
copyLen++;
|
||||
}
|
||||
}
|
||||
|
||||
localPreChar = c;
|
||||
preChar = c;
|
||||
}
|
||||
|
||||
// restore fields
|
||||
bufPos = localBufPos;
|
||||
preChar = localPreChar;
|
||||
copyStart = localCopyStart;
|
||||
this.preChar = preChar;
|
||||
|
||||
return currentFields;
|
||||
}
|
||||
@ -259,4 +287,99 @@ public final class CsvParser implements Closeable, Serializable {
|
||||
public void close() throws IOException {
|
||||
reader.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字段加入字段列表并自动去包装和去转义
|
||||
*
|
||||
* @param currentFields 当前的字段列表(即为行)
|
||||
* @param field 字段
|
||||
*/
|
||||
private void addField(List<String> currentFields, String field) {
|
||||
field = StrUtil.unWrap(field, config.textDelimiter);
|
||||
char textDelimiter = this.config.textDelimiter;
|
||||
field = StrUtil.replace(field, "" + textDelimiter + textDelimiter, textDelimiter + "");
|
||||
currentFields.add(StrUtil.unWrap(field, textDelimiter));
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部Buffer
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
private static class Buffer {
|
||||
final char[] buf;
|
||||
|
||||
/**
|
||||
* 标记位置,用于读数据
|
||||
*/
|
||||
private int mark;
|
||||
/**
|
||||
* 当前位置
|
||||
*/
|
||||
private int position;
|
||||
/**
|
||||
* 读取的数据长度,一般小于buf.length,-1表示无数据
|
||||
*/
|
||||
private int limit;
|
||||
|
||||
Buffer(int capacity) {
|
||||
buf = new char[capacity];
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否还有未读数据
|
||||
*
|
||||
* @return 是否还有未读数据
|
||||
*/
|
||||
public final boolean hasRemaining() {
|
||||
return position < limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取到缓存
|
||||
*
|
||||
* @param reader {@link Reader}
|
||||
*/
|
||||
int read(Reader reader) {
|
||||
int length;
|
||||
try {
|
||||
length = reader.read(this.buf);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
this.mark = 0;
|
||||
this.position = 0;
|
||||
this.limit = length;
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 先获取当前字符,再将当前位置后移一位<br>
|
||||
* 此方法不检查是否到了数组末尾,请自行使用{@link #hasRemaining()}判断。
|
||||
*
|
||||
* @return 当前位置字符
|
||||
* @see #hasRemaining()
|
||||
*/
|
||||
char get() {
|
||||
return this.buf[this.position++];
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记位置记为下次读取位置
|
||||
*/
|
||||
void mark() {
|
||||
this.mark = this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据追加到{@link StrBuilder},追加结束后需手动调用{@link #mark()} 重置读取位置
|
||||
*
|
||||
* @param builder {@link StrBuilder}
|
||||
* @param length 追加的长度
|
||||
* @see #mark()
|
||||
*/
|
||||
void appendTo(StrBuilder builder, int length) {
|
||||
builder.append(this.buf, this.mark, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,14 +29,21 @@ import java.util.Collection;
|
||||
public final class CsvWriter implements Closeable, Flushable, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 写出器 */
|
||||
/**
|
||||
* 写出器
|
||||
*/
|
||||
private final Writer writer;
|
||||
/** 写出配置 */
|
||||
/**
|
||||
* 写出配置
|
||||
*/
|
||||
private final CsvWriteConfig config;
|
||||
/** 是否处于新行开始 */
|
||||
/**
|
||||
* 是否处于新行开始
|
||||
*/
|
||||
private boolean newline = true;
|
||||
|
||||
// --------------------------------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造,覆盖已有文件(如果存在),默认编码UTF-8
|
||||
*
|
||||
@ -198,18 +205,56 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出一行
|
||||
*
|
||||
* @param fields 字段列表 ({@code null} 值会被做为空值追加)
|
||||
* @return this
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public CsvWriter writeLine(String... fields) throws IORuntimeException {
|
||||
if (ArrayUtil.isEmpty(fields)) {
|
||||
return writeLine();
|
||||
}
|
||||
appendLine(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加新行(换行)
|
||||
*
|
||||
* @return this
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void writeLine() throws IORuntimeException {
|
||||
public CsvWriter writeLine() throws IORuntimeException {
|
||||
try {
|
||||
writer.write(config.lineDelimiter);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
newline = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出一行注释,注释符号可自定义
|
||||
*
|
||||
* @param comment 注释内容
|
||||
* @return this
|
||||
* @see CsvConfig#commentCharacter
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public CsvWriter writeComment(String comment) {
|
||||
try {
|
||||
writer.write(this.config.commentCharacter);
|
||||
writer.write(comment);
|
||||
writer.write(config.lineDelimiter);
|
||||
newline = true;
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -227,13 +272,14 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 追加一行,末尾会自动换行,但是追加前不会换行
|
||||
*
|
||||
* @param fields 字段列表 ({@code null} 值会被做为空值追加)
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private void appendLine(final String... fields) throws IORuntimeException {
|
||||
private void appendLine(String... fields) throws IORuntimeException {
|
||||
try {
|
||||
doAppendLine(fields);
|
||||
} catch (IOException e) {
|
||||
@ -276,7 +322,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
|
||||
|
||||
if (null == value) {
|
||||
if (alwaysDelimitText) {
|
||||
writer.write(new char[] { textDelimiter, textDelimiter });
|
||||
writer.write(new char[]{textDelimiter, textDelimiter});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -523,7 +523,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆数组,如果非数组返回<code>null</code>
|
||||
* 克隆数组,如果非数组返回{@code null}
|
||||
*
|
||||
* @param <T> 数组元素类型
|
||||
* @param obj 数组对象
|
||||
@ -1667,11 +1667,6 @@ public class ArrayUtil extends PrimitiveArrayUtil {
|
||||
Assert.isTrue(isArray(array1), "First is not a Array !");
|
||||
Assert.isTrue(isArray(array2), "Second is not a Array !");
|
||||
|
||||
// 数组类型一致性判断
|
||||
if (array1.getClass() != array2.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array1 instanceof long[]) {
|
||||
return Arrays.equals((long[]) array1, (long[]) array2);
|
||||
} else if (array1 instanceof int[]) {
|
||||
|
@ -47,7 +47,7 @@ public class CharUtil {
|
||||
public static final char AMP = '&';
|
||||
/** 字符常量:冒号 {@code ':'} */
|
||||
public static final char COLON = ':';
|
||||
/** 字符常量:艾特 <code>'@'</code> */
|
||||
/** 字符常量:艾特 {@code '@'} */
|
||||
public static final char AT = '@';
|
||||
|
||||
/**
|
||||
|
@ -147,7 +147,7 @@ public class IdcardUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有效身份证号
|
||||
* 是否有效身份证号,忽略X的大小写
|
||||
*
|
||||
* @param idCard 身份证号,支持18位、15位和港澳台的10位
|
||||
* @return 是否有效
|
||||
@ -198,9 +198,46 @@ public class IdcardUtil {
|
||||
* </ol>
|
||||
*
|
||||
* @param idcard 待验证的身份证
|
||||
* @return 是否有效的18位身份证
|
||||
* @return 是否有效的18位身份证,忽略x的大小写
|
||||
*/
|
||||
public static boolean isValidCard18(String idcard) {
|
||||
return isValidCard18(idcard, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 判断18位身份证的合法性
|
||||
* </p>
|
||||
* 根据〖中华人民共和国国家标准GB11643-1999〗中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。<br>
|
||||
* 排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
|
||||
* <p>
|
||||
* 顺序码: 表示在同一地址码所标识的区域范围内,对同年、同月、同 日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配 给女性。
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>第1、2位数字表示:所在省份的代码</li>
|
||||
* <li>第3、4位数字表示:所在城市的代码</li>
|
||||
* <li>第5、6位数字表示:所在区县的代码</li>
|
||||
* <li>第7~14位数字表示:出生年、月、日</li>
|
||||
* <li>第15、16位数字表示:所在地的派出所的代码</li>
|
||||
* <li>第17位数字表示性别:奇数表示男性,偶数表示女性</li>
|
||||
* <li>第18位数字是校检码,用来检验身份证的正确性。校检码可以是0~9的数字,有时也用x表示</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* 第十八位数字(校验码)的计算方法为:
|
||||
* <ol>
|
||||
* <li>将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2</li>
|
||||
* <li>将这17位数字和系数相乘的结果相加</li>
|
||||
* <li>用加出来和除以11,看余数是多少</li>
|
||||
* <li>余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3 2</li>
|
||||
* <li>通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param idcard 待验证的身份证
|
||||
* @param ignoreCase 是否忽略大小写。{@code true}则忽略X大小写,否则严格匹配大写。
|
||||
* @return 是否有效的18位身份证
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public static boolean isValidCard18(String idcard, boolean ignoreCase) {
|
||||
if (CHINA_ID_MAX_LENGTH != idcard.length()) {
|
||||
return false;
|
||||
}
|
||||
@ -217,13 +254,12 @@ public class IdcardUtil {
|
||||
}
|
||||
|
||||
// 前17位
|
||||
String code17 = idcard.substring(0, 17);
|
||||
// 第18位
|
||||
char code18 = Character.toLowerCase(idcard.charAt(17));
|
||||
final String code17 = idcard.substring(0, 17);
|
||||
if (ReUtil.isMatch(PatternPool.NUMBERS, code17)) {
|
||||
// 获取校验位
|
||||
char val = getCheckCode18(code17);
|
||||
return val == code18;
|
||||
// 第18位
|
||||
return CharUtil.equals(val, idcard.charAt(17), ignoreCase);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -586,7 +622,7 @@ public class IdcardUtil {
|
||||
case 3:
|
||||
return '9';
|
||||
case 2:
|
||||
return 'x';
|
||||
return 'X';
|
||||
case 1:
|
||||
return '0';
|
||||
case 0:
|
||||
|
@ -14,11 +14,6 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class PhoneUtil {
|
||||
|
||||
/**
|
||||
* 座机号码
|
||||
*/
|
||||
private static final Pattern TEL = Pattern.compile("0\\d{2,3}-[1-9]\\d{6,7}");
|
||||
|
||||
/**
|
||||
* 验证是否为手机号码(中国)
|
||||
*
|
||||
@ -38,7 +33,7 @@ public class PhoneUtil {
|
||||
* @since 5.3.11
|
||||
*/
|
||||
public static boolean isTel(CharSequence value) {
|
||||
return Validator.isMatchRegex(TEL, value);
|
||||
return Validator.isMatchRegex(PatternPool.TEL, value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -298,7 +298,7 @@ public class RuntimeUtil {
|
||||
*
|
||||
* @return 最大可用内存
|
||||
*/
|
||||
public final long getUsableMemory() {
|
||||
public static long getUsableMemory() {
|
||||
return getMaxMemory() - getTotalMemory() + getFreeMemory();
|
||||
}
|
||||
}
|
||||
|
@ -644,7 +644,7 @@ public class ZipUtil {
|
||||
outItemFile.mkdirs();
|
||||
} else {
|
||||
// 文件
|
||||
FileUtil.writeFromStream(zipStream, outItemFile);
|
||||
FileUtil.writeFromStream(zipStream, outItemFile, false);
|
||||
}
|
||||
});
|
||||
return outFile;
|
||||
|
@ -1,19 +1,18 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.bean.DynaBean;
|
||||
|
||||
/**
|
||||
* {@link DynaBean}单元测试
|
||||
* @author Looly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class DynaBeanTest {
|
||||
|
||||
@Test
|
||||
public void beanTest(){
|
||||
public void beanTest() {
|
||||
User user = new User();
|
||||
DynaBean bean = DynaBean.create(user);
|
||||
bean.set("name", "李华");
|
||||
@ -34,29 +33,63 @@ public class DynaBeanTest {
|
||||
Assert.assertEquals("test for 李华", invoke);
|
||||
}
|
||||
|
||||
public static class User{
|
||||
|
||||
@Test
|
||||
public void beanByStaticClazzConstructorTest() {
|
||||
String name_before = "李华";
|
||||
int age_before = 12;
|
||||
DynaBean bean = DynaBean.create(User.class);
|
||||
bean.set("name", name_before);
|
||||
bean.set("age", age_before);
|
||||
|
||||
String name_after = bean.get("name");
|
||||
Assert.assertEquals(name_before, name_after);
|
||||
int age_after = bean.get("age");
|
||||
Assert.assertEquals(age_before, age_after);
|
||||
|
||||
//重复包装测试
|
||||
DynaBean bean2 = new DynaBean(bean);
|
||||
User user2 = bean2.getBean();
|
||||
User user1 = bean.getBean();
|
||||
Assert.assertEquals(user1, user2);
|
||||
|
||||
//执行指定方法
|
||||
Object invoke = bean2.invoke("testMethod");
|
||||
Assert.assertEquals("test for 李华", invoke);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void beanByInstanceClazzConstructorTest() {
|
||||
String name_before = "李华";
|
||||
int age_before = 12;
|
||||
DynaBean bean = new DynaBean(User.class);
|
||||
bean.set("name", name_before);
|
||||
bean.set("age", age_before);
|
||||
|
||||
String name_after = bean.get("name");
|
||||
Assert.assertEquals(name_before, name_after);
|
||||
int age_after = bean.get("age");
|
||||
Assert.assertEquals(age_before, age_after);
|
||||
|
||||
//重复包装测试
|
||||
DynaBean bean2 = new DynaBean(bean);
|
||||
User user2 = bean2.getBean();
|
||||
User user1 = bean.getBean();
|
||||
Assert.assertEquals(user1, user2);
|
||||
|
||||
//执行指定方法
|
||||
Object invoke = bean2.invoke("testMethod");
|
||||
Assert.assertEquals("test for 李华", invoke);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class User {
|
||||
private String name;
|
||||
private int age;
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String testMethod(){
|
||||
public String testMethod() {
|
||||
return "test for " + this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User [name=" + name + ", age=" + age + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -376,6 +376,15 @@ public class FileUtilTest {
|
||||
public void getMimeTypeTest() {
|
||||
String mimeType = FileUtil.getMimeType("test2Write.jpg");
|
||||
Assert.assertEquals("image/jpeg", mimeType);
|
||||
|
||||
mimeType = FileUtil.getMimeType("test2Write.html");
|
||||
Assert.assertEquals("text/html", mimeType);
|
||||
|
||||
mimeType = FileUtil.getMimeType("main.css");
|
||||
Assert.assertEquals("text/css", mimeType);
|
||||
|
||||
mimeType = FileUtil.getMimeType("test.js");
|
||||
Assert.assertEquals("application/x-javascript", mimeType);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -25,7 +25,16 @@ public class PathUtilTest {
|
||||
public void copyTest(){
|
||||
PathUtil.copy(
|
||||
Paths.get("d:/Red2_LYY"),
|
||||
Paths.get("d:/test/")
|
||||
Paths.get("d:/test/aaa/aaa.txt")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void copyContentTest(){
|
||||
PathUtil.copyContent(
|
||||
Paths.get("d:/Red2_LYY"),
|
||||
Paths.get("d:/test/aaa/")
|
||||
);
|
||||
}
|
||||
|
||||
@ -37,7 +46,10 @@ public class PathUtilTest {
|
||||
|
||||
@Test
|
||||
public void getMimeTypeTest(){
|
||||
final String mimeType = PathUtil.getMimeType(Paths.get("d:/test/test.jpg"));
|
||||
String mimeType = PathUtil.getMimeType(Paths.get("d:/test/test.jpg"));
|
||||
Assert.assertEquals("image/jpeg", mimeType);
|
||||
|
||||
mimeType = PathUtil.getMimeType(Paths.get("d:/test/test.mov"));
|
||||
Assert.assertEquals("video/quicktime", mimeType);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.thread.ConcurrencyTester;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@ -66,4 +67,12 @@ public class SnowflakeTest {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSnowflakeLengthTest(){
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
final long l = IdUtil.getSnowflake(0, 0).nextId();
|
||||
Assert.assertEquals(19, StrUtil.toString(l).length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
package cn.hutool.core.text.csv;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -16,21 +17,41 @@ public class CsvUtilTest {
|
||||
//从文件中读取CSV数据
|
||||
CsvData data = reader.read(FileUtil.file("test.csv"));
|
||||
List<CsvRow> rows = data.getRows();
|
||||
for (CsvRow csvRow : rows) {
|
||||
Assert.notEmpty(csvRow.getRawList());
|
||||
}
|
||||
final CsvRow row0 = rows.get(0);
|
||||
Assert.assertEquals("sss,sss", row0.get(0));
|
||||
Assert.assertEquals("姓名", row0.get(1));
|
||||
Assert.assertEquals("性别", row0.get(2));
|
||||
Assert.assertEquals("关注\"对象\"", row0.get(3));
|
||||
Assert.assertEquals("年龄", row0.get(4));
|
||||
Assert.assertEquals("", row0.get(5));
|
||||
Assert.assertEquals("\"", row0.get(6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTest2() {
|
||||
CsvReader reader = CsvUtil.getReader();
|
||||
reader.read(FileUtil.getUtf8Reader("test.csv"), (csvRow)-> Assert.notEmpty(csvRow.getRawList()));
|
||||
reader.read(FileUtil.getUtf8Reader("test.csv"), (csvRow)-> {
|
||||
// 只有一行,所以直接判断
|
||||
Assert.assertEquals("sss,sss", csvRow.get(0));
|
||||
Assert.assertEquals("姓名", csvRow.get(1));
|
||||
Assert.assertEquals("性别", csvRow.get(2));
|
||||
Assert.assertEquals("关注\"对象\"", csvRow.get(3));
|
||||
Assert.assertEquals("年龄", csvRow.get(4));
|
||||
Assert.assertEquals("", csvRow.get(5));
|
||||
Assert.assertEquals("\"", csvRow.get(6));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readTest3() {
|
||||
CsvReader reader = CsvUtil.getReader();
|
||||
reader.read(FileUtil.getUtf8Reader("test.csv"), Console::log);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void writeTest() {
|
||||
CsvWriter writer = CsvUtil.getWriter("e:/testWrite.csv", CharsetUtil.CHARSET_UTF_8);
|
||||
CsvWriter writer = CsvUtil.getWriter("d:/test/testWrite.csv", CharsetUtil.CHARSET_UTF_8);
|
||||
writer.write(
|
||||
new String[] {"a1", "b1", "c1", "123345346456745756756785656"},
|
||||
new String[] {"a2", "b2", "c2"},
|
||||
@ -38,4 +59,13 @@ public class CsvUtilTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void readLfTest(){
|
||||
final CsvReader reader = CsvUtil.getReader();
|
||||
final CsvData read = reader.read(FileUtil.file("d:/test/rw_test.csv"));
|
||||
for (CsvRow row : read) {
|
||||
Console.log(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class IdcardUtilTest {
|
||||
Assert.assertEquals("150102198807303035", convert15To18);
|
||||
|
||||
String convert15To18Second = IdcardUtil.convert15To18("330102200403064");
|
||||
Assert.assertEquals("33010219200403064x", convert15To18Second);
|
||||
Assert.assertEquals("33010219200403064X", convert15To18Second);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -89,8 +89,20 @@ public class IdcardUtilTest {
|
||||
|
||||
@Test
|
||||
public void isValidCard18Test(){
|
||||
final boolean isValidCard18 = IdcardUtil.isValidCard18("3301022011022000D6");
|
||||
boolean isValidCard18 = IdcardUtil.isValidCard18("3301022011022000D6");
|
||||
Assert.assertFalse(isValidCard18);
|
||||
|
||||
// 不忽略大小写情况下,X严格校验必须大写
|
||||
isValidCard18 = IdcardUtil.isValidCard18("33010219200403064x", false);
|
||||
Assert.assertFalse(isValidCard18);
|
||||
isValidCard18 = IdcardUtil.isValidCard18("33010219200403064X", false);
|
||||
Assert.assertTrue(isValidCard18);
|
||||
|
||||
// 非严格校验下大小写皆可
|
||||
isValidCard18 = IdcardUtil.isValidCard18("33010219200403064x");
|
||||
Assert.assertTrue(isValidCard18);
|
||||
isValidCard18 = IdcardUtil.isValidCard18("33010219200403064X");
|
||||
Assert.assertTrue(isValidCard18);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,11 +1,10 @@
|
||||
package cn.hutool.core.util;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.RuntimeUtil;
|
||||
|
||||
/**
|
||||
* 命令行单元测试
|
||||
* @author looly
|
||||
@ -26,4 +25,9 @@ public class RuntimeUtilTest {
|
||||
String str = RuntimeUtil.execForStr("cmd /c dir");
|
||||
Console.log(str);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUsableMemoryTest(){
|
||||
Assert.assertTrue(RuntimeUtil.getUsableMemory() > 0);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.core.util;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
@ -8,6 +9,7 @@ import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
@ -102,8 +104,9 @@ public class ZipUtilTest {
|
||||
try (OutputStream out = new FileOutputStream(zip)){
|
||||
//实际应用中, out 为 HttpServletResponse.getOutputStream
|
||||
ZipUtil.zip(out, Charset.defaultCharset(), false, null, new File(dir));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
"sss,sss",姓名,"性别",关注"对象",年龄
|
||||
# 这是一行注释,读取时应忽略
|
||||
"sss,sss",姓名,"性别",关注"对象",年龄,"","""
|
Can't render this file because it contains an unexpected character in line 1 and column 33.
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
@ -8,7 +8,7 @@ package cn.hutool.crypto.symmetric;
|
||||
*
|
||||
*/
|
||||
public enum SymmetricAlgorithm {
|
||||
/** 默认的AES加密方式:AES/CBC/PKCS5Padding */
|
||||
/** 默认的AES加密方式:AES/ECB/PKCS5Padding */
|
||||
AES("AES"),
|
||||
ARCFOUR("ARCFOUR"),
|
||||
Blowfish("Blowfish"),
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
@ -21,12 +21,12 @@
|
||||
<c3p0.version>0.9.5.5</c3p0.version>
|
||||
<dbcp2.version>2.8.0</dbcp2.version>
|
||||
<tomcat-jdbc.version>9.0.30</tomcat-jdbc.version>
|
||||
<druid.version>1.2.3</druid.version>
|
||||
<druid.version>1.2.4</druid.version>
|
||||
<hikariCP.version>2.4.13</hikariCP.version>
|
||||
<mongo.version>3.12.7</mongo.version>
|
||||
<sqlite.version>3.32.3.2</sqlite.version>
|
||||
<sqlite.version>3.34.0</sqlite.version>
|
||||
<hsqldb.version>2.5.1</hsqldb.version>
|
||||
<jedis.version>3.3.0</jedis.version>
|
||||
<jedis.version>3.4.1</jedis.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
@ -23,14 +23,14 @@
|
||||
<rythm.version>1.3.0</rythm.version>
|
||||
<freemarker.version>2.3.30</freemarker.version>
|
||||
<enjoy.version>4.9.03</enjoy.version>
|
||||
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
|
||||
<thymeleaf.version>3.0.12.RELEASE</thymeleaf.version>
|
||||
<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>
|
||||
<emoji-java.version>5.1.1</emoji-java.version>
|
||||
<servlet-api.version>4.0.1</servlet-api.version>
|
||||
<spring-boot.version>2.4.0</spring-boot.version>
|
||||
<spring-boot.version>2.4.1</spring-boot.version>
|
||||
<cglib.version>3.3.0</cglib.version>
|
||||
</properties>
|
||||
|
||||
@ -406,7 +406,7 @@
|
||||
<dependency>
|
||||
<groupId>org.mvel</groupId>
|
||||
<artifactId>mvel2</artifactId>
|
||||
<version>2.4.10.Final</version>
|
||||
<version>2.4.11.Final</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
@ -420,7 +420,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<version>5.3.2</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
@ -119,7 +119,7 @@ public class StreamExtractor implements Extractor{
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
outItemFile.mkdirs();
|
||||
} else {
|
||||
FileUtil.writeFromStream(in, outItemFile);
|
||||
FileUtil.writeFromStream(in, outItemFile, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.apache.commons.net.ftp.FTPClient;
|
||||
import org.apache.commons.net.ftp.FTPClientConfig;
|
||||
import org.apache.commons.net.ftp.FTPFile;
|
||||
import org.apache.commons.net.ftp.FTPReply;
|
||||
|
||||
@ -16,6 +17,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -81,7 +83,22 @@ public class Ftp extends AbstractFtp {
|
||||
* @param charset 编码
|
||||
*/
|
||||
public Ftp(String host, int port, String user, String password, Charset charset) {
|
||||
this(host, port, user, password, charset, null);
|
||||
this(host, port, user, password, charset, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param host 域名或IP
|
||||
* @param port 端口
|
||||
* @param user 用户名
|
||||
* @param password 密码
|
||||
* @param charset 编码
|
||||
* @param serverLanguageCode 服务器语言 例如:zh
|
||||
* @param systemKey 服务器标识 例如:org.apache.commons.net.ftp.FTPClientConfig.SYST_NT
|
||||
*/
|
||||
public Ftp(String host, int port, String user, String password, Charset charset, String serverLanguageCode, String systemKey) {
|
||||
this(host, port, user, password, charset, serverLanguageCode, systemKey, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,8 +111,8 @@ public class Ftp extends AbstractFtp {
|
||||
* @param charset 编码
|
||||
* @param mode 模式
|
||||
*/
|
||||
public Ftp(String host, int port, String user, String password, Charset charset, FtpMode mode) {
|
||||
this(new FtpConfig(host, port, user, password, charset), mode);
|
||||
public Ftp(String host, int port, String user, String password, Charset charset, String serverLanguageCode, String systemKey, FtpMode mode) {
|
||||
this(new FtpConfig(host, port, user, password, charset, serverLanguageCode, systemKey), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,7 +160,7 @@ public class Ftp extends AbstractFtp {
|
||||
* @return this
|
||||
*/
|
||||
public Ftp init(String host, int port, String user, String password, FtpMode mode) {
|
||||
return init(new FtpConfig(host, port, user, password, this.ftpConfig.getCharset()), mode);
|
||||
return init(new FtpConfig(host, port, user, password, this.ftpConfig.getCharset(), null, null), mode);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,8 +172,21 @@ public class Ftp extends AbstractFtp {
|
||||
*/
|
||||
public Ftp init(FtpConfig config, FtpMode mode) {
|
||||
final FTPClient client = new FTPClient();
|
||||
client.setControlEncoding(config.getCharset().toString());
|
||||
final Charset charset = config.getCharset();
|
||||
if (null != charset) {
|
||||
client.setControlEncoding(charset.toString());
|
||||
}
|
||||
client.setConnectTimeout((int) config.getConnectionTimeout());
|
||||
final String systemKey = config.getSystemKey();
|
||||
if (StrUtil.isNotBlank(systemKey)) {
|
||||
final FTPClientConfig conf = new FTPClientConfig(systemKey);
|
||||
final String serverLanguageCode = config.getServerLanguageCode();
|
||||
if (StrUtil.isNotBlank(serverLanguageCode)) {
|
||||
conf.setServerLanguageCode(config.getServerLanguageCode());
|
||||
}
|
||||
client.configure(conf);
|
||||
}
|
||||
|
||||
try {
|
||||
// 连接ftp服务器
|
||||
client.connect(config.getHost(), config.getPort());
|
||||
@ -344,6 +374,7 @@ public class Ftp extends AbstractFtp {
|
||||
|
||||
/**
|
||||
* 获取服务端目录状态。
|
||||
*
|
||||
* @param path 路径
|
||||
* @return 状态int,服务端不同,返回不同
|
||||
* @since 5.4.3
|
||||
@ -579,12 +610,29 @@ public class Ftp extends AbstractFtp {
|
||||
* @param out 输出位置
|
||||
*/
|
||||
public void download(String path, String fileName, OutputStream out) {
|
||||
download(path, fileName, out, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件到输出流
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @param fileName 文件名
|
||||
* @param out 输出位置
|
||||
* @param fileNameCharset 文件名编码
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public void download(String path, String fileName, OutputStream out, Charset fileNameCharset) {
|
||||
String pwd = null;
|
||||
if (this.backToPwd) {
|
||||
pwd = pwd();
|
||||
}
|
||||
|
||||
cd(path);
|
||||
|
||||
if (null != fileNameCharset) {
|
||||
fileName = new String(fileName.getBytes(fileNameCharset), StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
try {
|
||||
client.setFileType(FTPClient.BINARY_FILE_TYPE);
|
||||
client.retrieveFile(fileName, out);
|
||||
|
@ -11,7 +11,7 @@ import java.nio.charset.Charset;
|
||||
public class FtpConfig implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static FtpConfig create(){
|
||||
public static FtpConfig create() {
|
||||
return new FtpConfig();
|
||||
}
|
||||
|
||||
@ -46,6 +46,16 @@ public class FtpConfig implements Serializable {
|
||||
*/
|
||||
private long soTimeout;
|
||||
|
||||
/**
|
||||
* 设置服务器语言
|
||||
*/
|
||||
private String serverLanguageCode;
|
||||
|
||||
/**
|
||||
* 设置服务器系统关键词
|
||||
*/
|
||||
private String systemKey;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
@ -62,11 +72,29 @@ public class FtpConfig implements Serializable {
|
||||
* @param charset 编码
|
||||
*/
|
||||
public FtpConfig(String host, int port, String user, String password, Charset charset) {
|
||||
this(host, port, user, password, charset, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param host 主机
|
||||
* @param port 端口
|
||||
* @param user 用户名
|
||||
* @param password 密码
|
||||
* @param charset 编码
|
||||
* @param serverLanguageCode 服务器语言
|
||||
* @param systemKey 系统关键字
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public FtpConfig(String host, int port, String user, String password, Charset charset, String serverLanguageCode, String systemKey) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.charset = charset;
|
||||
this.serverLanguageCode = serverLanguageCode;
|
||||
this.systemKey = systemKey;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
@ -131,4 +159,22 @@ public class FtpConfig implements Serializable {
|
||||
this.soTimeout = soTimeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getServerLanguageCode() {
|
||||
return serverLanguageCode;
|
||||
}
|
||||
|
||||
public FtpConfig setServerLanguageCode(String serverLanguageCode) {
|
||||
this.serverLanguageCode = serverLanguageCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSystemKey() {
|
||||
return systemKey;
|
||||
}
|
||||
|
||||
public FtpConfig setSystemKey(String systemKey) {
|
||||
this.systemKey = systemKey;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import javax.activation.DataSource;
|
||||
import javax.activation.FileDataSource;
|
||||
import javax.activation.FileTypeMap;
|
||||
import javax.mail.Address;
|
||||
import javax.mail.Authenticator;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Multipart;
|
||||
import javax.mail.SendFailedException;
|
||||
@ -25,6 +24,7 @@ import javax.mail.util.ByteArrayDataSource;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
@ -77,11 +77,16 @@ public class Mail {
|
||||
*/
|
||||
private boolean useGlobalSession = false;
|
||||
|
||||
/**
|
||||
* debug输出位置,可以自定义debug日志
|
||||
*/
|
||||
private PrintStream debugOutput;
|
||||
|
||||
/**
|
||||
* 创建邮件客户端
|
||||
*
|
||||
* @param mailAccount 邮件帐号
|
||||
* @return {@link Mail}
|
||||
* @return Mail
|
||||
*/
|
||||
public static Mail create(MailAccount mailAccount) {
|
||||
return new Mail(mailAccount);
|
||||
@ -90,7 +95,7 @@ public class Mail {
|
||||
/**
|
||||
* 创建邮件客户端,使用全局邮件帐户
|
||||
*
|
||||
* @return {@link Mail}
|
||||
* @return Mail
|
||||
*/
|
||||
public static Mail create() {
|
||||
return new Mail();
|
||||
@ -345,6 +350,18 @@ public class Mail {
|
||||
this.useGlobalSession = isUseGlobalSession;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置debug输出位置,可以自定义debug日志
|
||||
*
|
||||
* @param debugOutput debug输出位置
|
||||
* @return this
|
||||
* @since 5.5.6
|
||||
*/
|
||||
public Mail setDebugOutput(PrintStream debugOutput) {
|
||||
this.debugOutput = debugOutput;
|
||||
return this;
|
||||
}
|
||||
// --------------------------------------------------------------- Getters and Setters end
|
||||
|
||||
/**
|
||||
@ -389,7 +406,7 @@ public class Mail {
|
||||
*/
|
||||
private MimeMessage buildMsg() throws MessagingException {
|
||||
final Charset charset = this.mailAccount.getCharset();
|
||||
final MimeMessage msg = new MimeMessage(getSession(this.useGlobalSession));
|
||||
final MimeMessage msg = new MimeMessage(getSession());
|
||||
// 发件人
|
||||
final String from = this.mailAccount.getFrom();
|
||||
if (StrUtil.isEmpty(from)) {
|
||||
@ -442,19 +459,16 @@ public class Mail {
|
||||
* 获取默认邮件会话<br>
|
||||
* 如果为全局单例的会话,则全局只允许一个邮件帐号,否则每次发送邮件会新建一个新的会话
|
||||
*
|
||||
* @param isSingleton 是否使用单例Session
|
||||
* @return 邮件会话 {@link Session}
|
||||
* @since 4.0.2
|
||||
*/
|
||||
private Session getSession(boolean isSingleton) {
|
||||
final MailAccount mailAccount = this.mailAccount;
|
||||
Authenticator authenticator = null;
|
||||
if (mailAccount.isAuth()) {
|
||||
authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
|
||||
private Session getSession() {
|
||||
final Session session = MailUtil.getSession(this.mailAccount, this.useGlobalSession);
|
||||
|
||||
if(null != this.debugOutput){
|
||||
session.setDebugOut(debugOutput);
|
||||
}
|
||||
|
||||
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
|
||||
: Session.getInstance(mailAccount.getSmtpProps(), authenticator);
|
||||
return session;
|
||||
}
|
||||
// --------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
@ -23,8 +23,10 @@ public class MailAccount implements Serializable {
|
||||
private static final String SMTP_CONNECTION_TIMEOUT = "mail.smtp.connectiontimeout";
|
||||
private static final String SMTP_TIMEOUT = "mail.smtp.timeout";
|
||||
|
||||
// SSL
|
||||
private static final String STARTTLS_ENABLE = "mail.smtp.starttls.enable";
|
||||
private static final String SSL_ENABLE = "mail.smtp.ssl.enable";
|
||||
private static final String SSL_PROTOCOLS = "mail.smtp.ssl.protocols";
|
||||
private static final String SOCKET_FACTORY = "mail.smtp.socketFactory.class";
|
||||
private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
|
||||
private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port";
|
||||
@ -80,6 +82,12 @@ public class MailAccount implements Serializable {
|
||||
* 使用 SSL安全连接
|
||||
*/
|
||||
private Boolean sslEnable;
|
||||
|
||||
/**
|
||||
* SSL协议,多个协议用空格分隔
|
||||
*/
|
||||
private String sslProtocols;
|
||||
|
||||
/**
|
||||
* 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
|
||||
*/
|
||||
@ -356,6 +364,25 @@ public class MailAccount implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SSL协议,多个协议用空格分隔
|
||||
* @return SSL协议,多个协议用空格分隔
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public String getSslProtocols() {
|
||||
return sslProtocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SSL协议,多个协议用空格分隔
|
||||
*
|
||||
* @param sslProtocols SSL协议,多个协议用空格分隔
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public void setSslProtocols(String sslProtocols) {
|
||||
this.sslProtocols = sslProtocols;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
|
||||
*
|
||||
@ -479,6 +506,10 @@ public class MailAccount implements Serializable {
|
||||
p.put(SOCKET_FACTORY, socketFactoryClass);
|
||||
p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback));
|
||||
p.put(SOCKET_FACTORY_PORT, String.valueOf(this.socketFactoryPort));
|
||||
// issue#IZN95@Gitee,在Linux下需自定义SSL协议版本
|
||||
if(StrUtil.isNotBlank(this.sslProtocols)){
|
||||
p.put(SSL_PROTOCOLS, this.sslProtocols);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
|
@ -5,6 +5,8 @@ import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import javax.mail.Authenticator;
|
||||
import javax.mail.Session;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
@ -346,6 +348,24 @@ public class MailUtil {
|
||||
return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置文件,获取邮件客户端会话
|
||||
*
|
||||
* @param mailAccount 邮件账户配置
|
||||
* @param isSingleton 是否单例(全局共享会话)
|
||||
* @return {@link Session}
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public static Session getSession(MailAccount mailAccount, boolean isSingleton){
|
||||
Authenticator authenticator = null;
|
||||
if (mailAccount.isAuth()) {
|
||||
authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
|
||||
}
|
||||
|
||||
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
|
||||
: Session.getInstance(mailAccount.getSmtpProps(), authenticator);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------------------------ Private method start
|
||||
|
||||
/**
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -1064,10 +1064,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
|
||||
this.httpConnection = HttpConnection
|
||||
.create(this.url.toURL(this.urlHandler), this.proxy)//
|
||||
.setMethod(this.method)//
|
||||
.setHttpsInfo(this.hostnameVerifier, this.ssf)//
|
||||
.setConnectTimeout(this.connectionTimeout)//
|
||||
.setReadTimeout(this.readTimeout)//
|
||||
.setMethod(this.method)//
|
||||
.setHttpsInfo(this.hostnameVerifier, this.ssf)//
|
||||
// 定义转发
|
||||
.setInstanceFollowRedirects(this.maxRedirectCount > 0)
|
||||
// 流方式上传数据
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import com.sun.net.httpserver.HttpContext;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
@ -34,4 +35,14 @@ public class HttpServerBase {
|
||||
public HttpExchange getHttpExchange() {
|
||||
return this.httpExchange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link HttpContext}
|
||||
*
|
||||
* @return {@link HttpContext}
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public HttpContext getHttpContext() {
|
||||
return getHttpExchange().getHttpContext();
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP状态码
|
||||
* 发送HTTP状态码,Content-Length为0不定长度,会输出Transfer-encoding: chunked
|
||||
*
|
||||
* @param httpStatusCode HTTP状态码,见HttpStatus
|
||||
* @return this
|
||||
@ -64,6 +64,17 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
return send(HttpStatus.HTTP_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送成功状态码
|
||||
*
|
||||
* @param bodyLength 响应体长度,默认0表示不定长度,会输出Transfer-encoding: chunked
|
||||
* @return this
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public HttpServerResponse sendOk(int bodyLength) {
|
||||
return send(HttpStatus.HTTP_OK, bodyLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送404错误页
|
||||
*
|
||||
@ -91,7 +102,7 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
* 发送HTTP状态码
|
||||
*
|
||||
* @param httpStatusCode HTTP状态码,见HttpStatus
|
||||
* @param bodyLength 响应体长度,默认0
|
||||
* @param bodyLength 响应体长度,默认0表示不定长度,会输出Transfer-encoding: chunked
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse send(int httpStatusCode, long bodyLength) {
|
||||
@ -290,7 +301,8 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(byte[] data) {
|
||||
return write(new ByteArrayInputStream(data));
|
||||
final ByteArrayInputStream in = new ByteArrayInputStream(data);
|
||||
return write(in, in.available());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,8 +314,21 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, String contentType) {
|
||||
return write(in, 0, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数据给客户端
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param length 内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked
|
||||
* @param contentType 返回的类型
|
||||
* @return this
|
||||
* @since 5.2.7
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, int length, String contentType) {
|
||||
setContentType(contentType);
|
||||
return write(in);
|
||||
return write(in, length);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -313,9 +338,23 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in) {
|
||||
return write(in, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param in 数据流
|
||||
* @param length 指定响应内容长度,默认0表示不定长度,会输出Transfer-encoding: chunked
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, int length) {
|
||||
if (false == isSendCode) {
|
||||
sendOk(Math.max(0, length));
|
||||
}
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = getOut();
|
||||
out = this.httpExchange.getResponseBody();
|
||||
IoUtil.copy(in, out);
|
||||
} finally {
|
||||
IoUtil.close(out);
|
||||
@ -332,12 +371,16 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public HttpServerResponse write(File file) {
|
||||
final long fileSize = file.length();
|
||||
if(fileSize > Integer.MAX_VALUE){
|
||||
throw new IllegalArgumentException("File size is too bigger than " + Integer.MAX_VALUE);
|
||||
}
|
||||
final String fileName = file.getName();
|
||||
final String contentType = ObjectUtil.defaultIfNull(HttpUtil.getMimeType(fileName), "application/octet-stream");
|
||||
BufferedInputStream in = null;
|
||||
try {
|
||||
in = FileUtil.getInputStream(file);
|
||||
write(in, contentType, fileName);
|
||||
write(in, (int)fileSize, contentType, fileName);
|
||||
} finally {
|
||||
IoUtil.close(in);
|
||||
}
|
||||
@ -353,12 +396,24 @@ public class HttpServerResponse extends HttpServerBase {
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public void write(InputStream in, String contentType, String fileName) {
|
||||
write(in, 0, contentType, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件数据给客户端(文件下载)
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param contentType 返回的类型
|
||||
* @param fileName 文件名
|
||||
* @since 5.2.7
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, int length, String contentType, String fileName) {
|
||||
final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET);
|
||||
|
||||
if(false == contentType.startsWith("text/")){
|
||||
if (false == contentType.startsWith("text/")) {
|
||||
// 非文本类型数据直接走下载
|
||||
setHeader(Header.CONTENT_DISPOSITION, StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, charset)));
|
||||
}
|
||||
write(in, contentType);
|
||||
return write(in, length, contentType);
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,26 @@ package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.GlobalThreadPool;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.server.action.Action;
|
||||
import cn.hutool.http.server.action.RootAction;
|
||||
import cn.hutool.http.server.filter.HttpFilter;
|
||||
import cn.hutool.http.server.filter.SimpleFilter;
|
||||
import cn.hutool.http.server.handler.ActionHandler;
|
||||
import com.sun.net.httpserver.Filter;
|
||||
import com.sun.net.httpserver.HttpContext;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import com.sun.net.httpserver.HttpsConfigurator;
|
||||
import com.sun.net.httpserver.HttpsServer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@ -21,7 +32,8 @@ import java.util.concurrent.Executor;
|
||||
*/
|
||||
public class SimpleServer {
|
||||
|
||||
HttpServer server;
|
||||
private final HttpServer server;
|
||||
private final List<Filter> filters;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@ -48,25 +60,107 @@ public class SimpleServer {
|
||||
* @param address 监听地址
|
||||
*/
|
||||
public SimpleServer(InetSocketAddress address) {
|
||||
this(address, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param address 监听地址
|
||||
* @param configurator https配置信息,用于使用自定义SSL(TLS)证书等
|
||||
*/
|
||||
public SimpleServer(InetSocketAddress address, HttpsConfigurator configurator) {
|
||||
try {
|
||||
if(null != configurator){
|
||||
final HttpsServer server = HttpsServer.create(address, 0);
|
||||
server.setHttpsConfigurator(configurator);
|
||||
this.server = server;
|
||||
} else{
|
||||
this.server = HttpServer.create(address, 0);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
setExecutor(GlobalThreadPool.getExecutor());
|
||||
filters = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求过滤器,此过滤器对所有请求有效<br>
|
||||
* 此方法需在以下方法前之前调用:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #setRoot(File)} </li>
|
||||
* <li>{@link #setRoot(String)} </li>
|
||||
* <li>{@link #createContext(String, HttpHandler)} </li>
|
||||
* <li>{@link #addHandler(String, HttpHandler)}</li>
|
||||
* <li>{@link #addAction(String, Action)} </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param filter {@link Filter} 请求过滤器
|
||||
* @return this
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public SimpleServer addFilter(Filter filter) {
|
||||
this.filters.add(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求过滤器,此过滤器对所有请求有效<br>
|
||||
* 此方法需在以下方法前之前调用:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #setRoot(File)} </li>
|
||||
* <li>{@link #setRoot(String)} </li>
|
||||
* <li>{@link #createContext(String, HttpHandler)} </li>
|
||||
* <li>{@link #addHandler(String, HttpHandler)}</li>
|
||||
* <li>{@link #addAction(String, Action)} </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param filter {@link Filter} 请求过滤器
|
||||
* @return this
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public SimpleServer addFilter(HttpFilter filter) {
|
||||
return addFilter(new SimpleFilter() {
|
||||
@Override
|
||||
public void doFilter(HttpExchange httpExchange, Chain chain) throws IOException {
|
||||
filter.doFilter(new HttpServerRequest(httpExchange), new HttpServerResponse(httpExchange), chain);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求处理规则
|
||||
*
|
||||
* @param path 路径
|
||||
* @param handler 处理器
|
||||
* @param path 路径,例如:/a/b 或者 a/b
|
||||
* @param handler 处理器,包括请求和响应处理
|
||||
* @return this
|
||||
* @see #createContext(String, HttpHandler)
|
||||
*/
|
||||
public SimpleServer addHandler(String path, HttpHandler handler) {
|
||||
this.server.createContext(path, handler);
|
||||
createContext(path, handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建请求映射上下文,创建后,用户访问指定路径可使用{@link HttpHandler} 中的规则进行处理
|
||||
*
|
||||
* @param path 路径,例如:/a/b 或者 a/b
|
||||
* @param handler 处理器,包括请求和响应处理
|
||||
* @return {@link HttpContext}
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public HttpContext createContext(String path, HttpHandler handler) {
|
||||
// 非/开头的路径会报错
|
||||
path = StrUtil.addPrefixIfNot(path, StrUtil.SLASH);
|
||||
final HttpContext context = this.server.createContext(path, handler);
|
||||
// 增加整体过滤器
|
||||
context.getFilters().addAll(this.filters);
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根目录,默认的页面从root目录中读取解析返回
|
||||
*
|
||||
|
@ -2,7 +2,6 @@ package cn.hutool.http.server.action;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
@ -80,7 +79,6 @@ public class RootAction implements Action {
|
||||
}
|
||||
}
|
||||
|
||||
Console.log(file.getAbsolutePath());
|
||||
response.send404("404 Not Found !");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* {@link com.sun.net.httpserver.HttpServer} 封装
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package cn.hutool.http.server.action;
|
@ -0,0 +1,26 @@
|
||||
package cn.hutool.http.server.filter;
|
||||
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import com.sun.net.httpserver.Filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 过滤器接口,用于简化{@link Filter} 使用
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.7
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface HttpFilter {
|
||||
|
||||
/**
|
||||
* 执行过滤
|
||||
* @param req {@link HttpServerRequest} 请求对象,用于获取请求内容
|
||||
* @param res {@link HttpServerResponse} 响应对象,用于写出内容
|
||||
* @param chain {@link Filter.Chain}
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
void doFilter(HttpServerRequest req, HttpServerResponse res, Filter.Chain chain) throws IOException;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package cn.hutool.http.server.filter;
|
||||
|
||||
import com.sun.net.httpserver.Filter;
|
||||
|
||||
/**
|
||||
* 匿名简单过滤器,跳过了描述
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public abstract class SimpleFilter extends Filter {
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Anonymous Filter";
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* {@link com.sun.net.httpserver.Filter} 实现包装
|
||||
*/
|
||||
package cn.hutool.http.server.filter;
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* {@link com.sun.net.httpserver.HttpHandler} 实现包装
|
||||
*/
|
||||
package cn.hutool.http.server.handler;
|
@ -1,19 +1,20 @@
|
||||
package cn.hutool.http.ssl;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static cn.hutool.http.ssl.SSLSocketFactoryBuilder.SSLv3;
|
||||
import static cn.hutool.http.ssl.SSLSocketFactoryBuilder.TLSv1;
|
||||
import static cn.hutool.http.ssl.SSLSocketFactoryBuilder.TLSv11;
|
||||
import static cn.hutool.http.ssl.SSLSocketFactoryBuilder.TLSv12;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* 兼容android低版本SSL连接
|
||||
* 咱在测试HttpUrlConnection的时候
|
||||
* 发现一部分手机无法连接[GithubPage]
|
||||
* 兼容android低版本SSL连接<br>
|
||||
* 在测试HttpUrlConnection的时候,发现一部分手机无法连接[GithubPage]
|
||||
*
|
||||
* <p>
|
||||
* 最后发现原来是某些SSL协议没有开启
|
||||
*
|
||||
* @author MikaGuraNTK
|
||||
*/
|
||||
public class AndroidSupportSSLFactory extends CustomProtocolsSSLFactory {
|
||||
|
@ -25,7 +25,7 @@ public class HttpRequestTest {
|
||||
@Test
|
||||
@Ignore
|
||||
public void getHttpsTest() {
|
||||
String body = HttpRequest.get("https://www.gjifa.com/pc/").execute().body();
|
||||
String body = HttpRequest.get("https://www.hutool.cn/").timeout(10).execute().body();
|
||||
Console.log(body);
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,6 @@ import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
@ -11,6 +11,10 @@ public class SimpleServerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpUtil.createServer(8888)
|
||||
.addFilter(((req, res, chain) -> {
|
||||
Console.log("Filter: " + req.getPath());
|
||||
chain.doFilter(req.getHttpExchange());
|
||||
}))
|
||||
// 设置默认根目录,classpath/html
|
||||
.setRoot(FileUtil.file("html"))
|
||||
// get数据测试,返回请求的PATH
|
||||
@ -45,6 +49,11 @@ public class SimpleServerTest {
|
||||
response.write(request.getMultipart().getParamMap().toString(), ContentType.TEXT_PLAIN.toString());
|
||||
}
|
||||
)
|
||||
// 测试输出响应内容是否能正常返回Content-Length头信息
|
||||
.addAction("test/zeroStr", (req, res)-> {
|
||||
res.write("0");
|
||||
Console.log("Write 0 OK");
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
@ -253,7 +253,7 @@ final class InternalJSONUtil {
|
||||
//默认使用时间戳
|
||||
long timeMillis;
|
||||
if (dateObj instanceof TemporalAccessor) {
|
||||
timeMillis = DateUtil.toInstant((TemporalAccessor) dateObj).toEpochMilli();
|
||||
timeMillis = TemporalAccessorUtil.toEpochMilli((TemporalAccessor) dateObj);
|
||||
} else if (dateObj instanceof Date) {
|
||||
timeMillis = ((Date) dateObj).getTime();
|
||||
} else if (dateObj instanceof Calendar) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
package cn.hutool.json;
|
||||
|
||||
/**
|
||||
* <code>JSONString</code>接口定义了一个<code>toJSONString()</code><br>
|
||||
* 实现此接口的类可以通过实现<code>toJSONString()</code>方法来改变转JSON字符串的方式。
|
||||
* {@code JSONString}接口定义了一个{@code toJSONString()}<br>
|
||||
* 实现此接口的类可以通过实现{@code toJSONString()}方法来改变转JSON字符串的方式。
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
|
@ -35,7 +35,7 @@ public class JSONSupport implements JSONString{
|
||||
* @return 美化的JSON
|
||||
*/
|
||||
public String toPrettyString() {
|
||||
return toJSON().toJSONString(4);
|
||||
return toJSON().toStringPretty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -218,7 +218,7 @@ public final class JSONUtil {
|
||||
json = (JSON) obj;
|
||||
} else if (obj instanceof CharSequence) {
|
||||
final String jsonStr = StrUtil.trim((CharSequence) obj);
|
||||
json = StrUtil.startWith(jsonStr, '[') ? parseArray(jsonStr) : parseObj(jsonStr);
|
||||
json = isJsonArray(jsonStr) ? parseArray(jsonStr, config) : parseObj(jsonStr, config);
|
||||
} else if (obj instanceof Iterable || obj instanceof Iterator || ArrayUtil.isArray(obj)) {// 列表
|
||||
json = new JSONArray(obj, config);
|
||||
} else {// 对象
|
||||
|
@ -11,6 +11,7 @@ import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.json.test.bean.Exam;
|
||||
import cn.hutool.json.test.bean.JsonNode;
|
||||
import cn.hutool.json.test.bean.KeyBean;
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@ -202,6 +203,14 @@ public class JSONArrayTest {
|
||||
Assert.assertEquals("-0", nodeList.get(3).getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getByPathTest(){
|
||||
String jsonStr = "[{\"id\": \"1\",\"name\": \"a\"},{\"id\": \"2\",\"name\": \"b\"}]";
|
||||
final JSONArray jsonArray = JSONUtil.parseArray(jsonStr);
|
||||
Assert.assertEquals("b", jsonArray.getByPath("[1].name"));
|
||||
Assert.assertEquals("b", JSONUtil.getByPath(jsonArray, "[1].name"));
|
||||
}
|
||||
|
||||
private static Map<String, String> buildMap(String id, String parentId, String name) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("id", id);
|
||||
@ -210,25 +219,9 @@ public class JSONArrayTest {
|
||||
return map;
|
||||
}
|
||||
|
||||
@Data
|
||||
static class User {
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User [id=" + id + ", name=" + name + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.json.test.bean.Price;
|
||||
import cn.hutool.json.test.bean.UserA;
|
||||
import cn.hutool.json.test.bean.UserC;
|
||||
@ -163,6 +164,14 @@ public class JSONUtilTest {
|
||||
Assert.assertEquals("12.00", jsonObject.getBigDecimal("test").setScale(2).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customValueTest() {
|
||||
final JSONObject jsonObject = JSONUtil.createObj()
|
||||
.set("test2", (JSONString) () -> NumberUtil.decimalFormat("#.0", 12.00D));
|
||||
|
||||
Assert.assertEquals("{\"test2\":12.0}", jsonObject.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseObjTest() {
|
||||
// 测试转义
|
||||
@ -171,4 +180,3 @@ public class JSONUtilTest {
|
||||
"}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-log</artifactId>
|
||||
@ -92,7 +92,7 @@
|
||||
<dependency>
|
||||
<groupId>org.tinylog</groupId>
|
||||
<artifactId>tinylog-impl</artifactId>
|
||||
<version>2.1.2</version>
|
||||
<version>2.2.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
|
@ -145,7 +145,7 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定坐标单元格,单元格不存在时返回<code>null</code>
|
||||
* 获取指定坐标单元格,单元格不存在时返回{@code null}
|
||||
*
|
||||
* @param locationRef 单元格地址标识符,例如A11,B5
|
||||
* @return {@link Cell}
|
||||
@ -157,7 +157,7 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定坐标单元格,单元格不存在时返回<code>null</code>
|
||||
* 获取指定坐标单元格,单元格不存在时返回{@code null}
|
||||
*
|
||||
* @param x X坐标,从0计数,即列号
|
||||
* @param y Y坐标,从0计数,即行号
|
||||
@ -193,7 +193,7 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定坐标单元格,如果isCreateIfNotExist为false,则在单元格不存在时返回<code>null</code>
|
||||
* 获取指定坐标单元格,如果isCreateIfNotExist为false,则在单元格不存在时返回{@code null}
|
||||
*
|
||||
* @param locationRef 单元格地址标识符,例如A11,B5
|
||||
* @param isCreateIfNotExist 单元格不存在时是否创建
|
||||
@ -206,7 +206,7 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定坐标单元格,如果isCreateIfNotExist为false,则在单元格不存在时返回<code>null</code>
|
||||
* 获取指定坐标单元格,如果isCreateIfNotExist为false,则在单元格不存在时返回{@code null}
|
||||
*
|
||||
* @param x X坐标,从0计数,即列号
|
||||
* @param y Y坐标,从0计数,即行号
|
||||
|
@ -233,7 +233,7 @@ public class ExcelWriter extends ExcelBase<ExcelWriter> {
|
||||
/**
|
||||
* 重命名sheet
|
||||
*
|
||||
* @param sheet sheet需要,0表示第一个sheet
|
||||
* @param sheet sheet序号,0表示第一个sheet
|
||||
* @param sheetName 新的sheet名
|
||||
* @return this
|
||||
* @since 4.1.8
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.poi.excel.sax;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.poi.excel.sax.handler.RowHandler;
|
||||
@ -116,12 +117,12 @@ public class Excel03SaxReader implements HSSFListener, ExcelSaxReader<Excel03Sax
|
||||
* 读取
|
||||
*
|
||||
* @param fs {@link POIFSFileSystem}
|
||||
* @param id sheet序号
|
||||
* @param id sheet序号,从0开始
|
||||
* @return this
|
||||
* @throws POIException IO异常包装
|
||||
*/
|
||||
public Excel03SaxReader read(POIFSFileSystem fs, String id) throws POIException {
|
||||
this.rid = Integer.parseInt(id);
|
||||
this.rid = getSheetIndex(id);
|
||||
|
||||
formatListener = new FormatTrackingHSSFListener(new MissingRecordAwareHSSFListener(this));
|
||||
final HSSFRequest request = new HSSFRequest();
|
||||
@ -341,5 +342,32 @@ public class Excel03SaxReader implements HSSFListener, ExcelSaxReader<Excel03Sax
|
||||
private boolean isProcessCurrentSheet() {
|
||||
return this.rid < 0 || this.curRid == this.rid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sheet索引,从0开始
|
||||
* <ul>
|
||||
* <li>传入'rId'开头,直接去除rId前缀</li>
|
||||
* <li>传入纯数字,表示sheetIndex,直接转换为rid</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param idOrRidOrSheetName Excel中的sheet id或者rid编号或sheet名称,从0开始,rid必须加rId前缀,例如rId0,如果为-1处理所有编号的sheet
|
||||
* @return sheet索引,从0开始
|
||||
* @since 5.5.5
|
||||
*/
|
||||
private int getSheetIndex(String idOrRidOrSheetName) {
|
||||
Assert.notBlank(idOrRidOrSheetName, "id or rid or sheetName must be not blank!");
|
||||
|
||||
// rid直接处理
|
||||
if (StrUtil.startWithIgnoreCase(idOrRidOrSheetName, RID_PREFIX)) {
|
||||
return Integer.parseInt(StrUtil.removePrefixIgnoreCase(idOrRidOrSheetName, RID_PREFIX));
|
||||
}
|
||||
|
||||
final int sheetIndex;
|
||||
try {
|
||||
return Integer.parseInt(idOrRidOrSheetName);
|
||||
} catch (NumberFormatException ignore) {
|
||||
throw new IllegalArgumentException("Invalid sheet id: " + idOrRidOrSheetName);
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package cn.hutool.poi.excel.sax;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.poi.excel.sax.handler.RowHandler;
|
||||
import cn.hutool.poi.exceptions.POIException;
|
||||
@ -25,8 +24,6 @@ import java.util.Iterator;
|
||||
*/
|
||||
public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
|
||||
// sheet r:Id前缀
|
||||
private static final String RID_PREFIX = "rId";
|
||||
private final SheetDataSaxHandler handler;
|
||||
|
||||
/**
|
||||
@ -56,9 +53,9 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Excel07SaxReader read(File file, String idOrRid) throws POIException {
|
||||
public Excel07SaxReader read(File file, String idOrRidOrSheetName) throws POIException {
|
||||
try {
|
||||
return read(OPCPackage.open(file), idOrRid);
|
||||
return read(OPCPackage.open(file), idOrRidOrSheetName);
|
||||
} catch (InvalidFormatException e) {
|
||||
throw new POIException(e);
|
||||
}
|
||||
@ -70,9 +67,9 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Excel07SaxReader read(InputStream in, String idOrRid) throws POIException {
|
||||
public Excel07SaxReader read(InputStream in, String idOrRidOrSheetName) throws POIException {
|
||||
try (final OPCPackage opcPackage = OPCPackage.open(in)) {
|
||||
return read(opcPackage, idOrRid);
|
||||
return read(opcPackage, idOrRidOrSheetName);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} catch (InvalidFormatException e) {
|
||||
@ -96,13 +93,13 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
* 开始读取Excel,Sheet编号从0开始计数
|
||||
*
|
||||
* @param opcPackage {@link OPCPackage},Excel包,读取后不关闭
|
||||
* @param idOrRid Excel中的sheet id或者rid编号,rid必须加rId前缀,例如rId1,如果为-1处理所有编号的sheet
|
||||
* @param idOrRidOrSheetName Excel中的sheet id或者rid编号或sheet名,rid必须加rId前缀,例如rId1,如果为-1处理所有编号的sheet
|
||||
* @return this
|
||||
* @throws POIException POI异常
|
||||
*/
|
||||
public Excel07SaxReader read(OPCPackage opcPackage, String idOrRid) throws POIException {
|
||||
public Excel07SaxReader read(OPCPackage opcPackage, String idOrRidOrSheetName) throws POIException {
|
||||
try {
|
||||
return read(new XSSFReader(opcPackage), idOrRid);
|
||||
return read(new XSSFReader(opcPackage), idOrRidOrSheetName);
|
||||
} catch (OpenXML4JException e) {
|
||||
throw new POIException(e);
|
||||
} catch (IOException e) {
|
||||
@ -114,12 +111,12 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
* 开始读取Excel,Sheet编号从0开始计数
|
||||
*
|
||||
* @param xssfReader {@link XSSFReader},Excel读取器
|
||||
* @param idOrRid Excel中的sheet id或者rid编号,rid必须加rId前缀,例如rId1,如果为-1处理所有编号的sheet
|
||||
* @param idOrRidOrSheetName Excel中的sheet id或者rid编号或sheet名,rid必须加rId前缀,例如rId1,如果为-1处理所有编号的sheet
|
||||
* @return this
|
||||
* @throws POIException POI异常
|
||||
* @since 5.4.4
|
||||
*/
|
||||
public Excel07SaxReader read(XSSFReader xssfReader, String idOrRid) throws POIException {
|
||||
public Excel07SaxReader read(XSSFReader xssfReader, String idOrRidOrSheetName) throws POIException {
|
||||
// 获取共享样式表,样式非必须
|
||||
try {
|
||||
this.handler.stylesTable = xssfReader.getStylesTable();
|
||||
@ -136,7 +133,7 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
throw new POIException(e);
|
||||
}
|
||||
|
||||
return readSheets(xssfReader, idOrRid);
|
||||
return readSheets(xssfReader, idOrRidOrSheetName);
|
||||
}
|
||||
// ------------------------------------------------------------------------------ Read end
|
||||
|
||||
@ -146,21 +143,13 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
* 开始读取Excel,Sheet编号从0开始计数
|
||||
*
|
||||
* @param xssfReader {@link XSSFReader},Excel读取器
|
||||
* @param idOrRid Excel中的sheet id或者rid编号,rid必须加rId前缀,例如rId0,如果为-1处理所有编号的sheet
|
||||
* @param idOrRidOrSheetName Excel中的sheet id或者rid编号或sheet名,从0开始,rid必须加rId前缀,例如rId0,如果为-1处理所有编号的sheet
|
||||
* @return this
|
||||
* @throws POIException POI异常
|
||||
* @since 5.4.4
|
||||
*/
|
||||
private Excel07SaxReader readSheets(XSSFReader xssfReader, String idOrRid) throws POIException {
|
||||
// 将sheetId转换为rid
|
||||
if (NumberUtil.isInteger(idOrRid)) {
|
||||
final SheetRidReader ridReader = new SheetRidReader();
|
||||
final String rid = ridReader.read(xssfReader).getRidBySheetId(idOrRid);
|
||||
if (StrUtil.isNotEmpty(rid)) {
|
||||
idOrRid = rid;
|
||||
}
|
||||
}
|
||||
this.handler.sheetIndex = Integer.parseInt(StrUtil.removePrefixIgnoreCase(idOrRid, RID_PREFIX));
|
||||
private Excel07SaxReader readSheets(XSSFReader xssfReader, String idOrRidOrSheetName) throws POIException {
|
||||
this.handler.sheetIndex = getSheetIndex(xssfReader, idOrRidOrSheetName);
|
||||
InputStream sheetInputStream = null;
|
||||
try {
|
||||
if (this.handler.sheetIndex > -1) {
|
||||
@ -190,5 +179,44 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取sheet索引,从0开始
|
||||
* <ul>
|
||||
* <li>传入'rId'开头,直接去除rId前缀</li>
|
||||
* <li>传入纯数字,表示sheetIndex,通过{@link SheetRidReader}转换为rId</li>
|
||||
* <li>传入其它字符串,表示sheetName,通过{@link SheetRidReader}转换为rId</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param xssfReader {@link XSSFReader},Excel读取器
|
||||
* @param idOrRidOrSheetName Excel中的sheet id或者rid编号或sheet名称,从0开始,rid必须加rId前缀,例如rId0,如果为-1处理所有编号的sheet
|
||||
* @return sheet索引,从0开始
|
||||
* @since 5.5.5
|
||||
*/
|
||||
private int getSheetIndex(XSSFReader xssfReader, String idOrRidOrSheetName) {
|
||||
// rid直接处理
|
||||
if (StrUtil.startWithIgnoreCase(idOrRidOrSheetName, RID_PREFIX)) {
|
||||
return Integer.parseInt(StrUtil.removePrefixIgnoreCase(idOrRidOrSheetName, RID_PREFIX));
|
||||
}
|
||||
|
||||
// sheetIndex需转换为rid
|
||||
final SheetRidReader ridReader = new SheetRidReader().read(xssfReader);
|
||||
|
||||
final int sheetIndex;
|
||||
Integer rid;
|
||||
try {
|
||||
sheetIndex = Integer.parseInt(idOrRidOrSheetName);
|
||||
rid = ridReader.getRidBySheetIdBase0(sheetIndex);
|
||||
return (null != rid) ? rid : sheetIndex;
|
||||
} catch (NumberFormatException ignore) {
|
||||
// 非数字,可能为sheet名称
|
||||
rid = ridReader.getRidByNameBase0(idOrRidOrSheetName);
|
||||
if (null != rid) {
|
||||
return rid;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Invalid rId or id or sheetName: " + idOrRidOrSheetName);
|
||||
}
|
||||
// --------------------------------------------------------------------------------------- Private method end
|
||||
}
|
@ -15,6 +15,9 @@ import java.io.InputStream;
|
||||
*/
|
||||
public interface ExcelSaxReader<T> {
|
||||
|
||||
// sheet r:Id前缀
|
||||
String RID_PREFIX = "rId";
|
||||
|
||||
/**
|
||||
* 开始读取Excel
|
||||
*
|
||||
|
@ -29,7 +29,7 @@ public class SheetDataSaxHandler extends DefaultHandler {
|
||||
protected StylesTable stylesTable;
|
||||
// excel 2007 的共享字符串表,对应sharedString.xml
|
||||
protected SharedStringsTable sharedStringsTable;
|
||||
// sheet的索引
|
||||
// sheet的索引,从0开始
|
||||
protected int sheetIndex;
|
||||
|
||||
// 当前非空行
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.poi.excel.sax;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@ -11,13 +12,13 @@ import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 在Sax方式读取Excel时,读取sheet标签中sheetId和rid的对应关系,类似于:
|
||||
* <pre>
|
||||
* <sheet name="Sheet6" sheetId="4" r:id="6"/>
|
||||
* <sheet name="Sheet6" sheetId="4" r:id="rId6"/>
|
||||
* </pre>
|
||||
* <p>
|
||||
* 读取结果为:
|
||||
@ -36,8 +37,8 @@ public class SheetRidReader extends DefaultHandler {
|
||||
private final static String SHEET_ID_ATTR = "sheetId";
|
||||
private final static String NAME_ATTR = "name";
|
||||
|
||||
private final Map<String, String> ID_RID_MAP = new HashMap<>();
|
||||
private final Map<String, String> NAME_RID_MAP = new HashMap<>();
|
||||
private final Map<Integer, Integer> ID_RID_MAP = new LinkedHashMap<>();
|
||||
private final Map<String, Integer> NAME_RID_MAP = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 读取Wordkbook的XML中sheet标签中sheetId和rid的对应关系
|
||||
@ -61,50 +62,100 @@ public class SheetRidReader extends DefaultHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据sheetId获取rid
|
||||
* 根据sheetId获取rid,从1开始
|
||||
*
|
||||
* @param sheetId Sheet的ID
|
||||
* @return rid
|
||||
* @param sheetId Sheet的ID,从1开始
|
||||
* @return rid,从1开始
|
||||
*/
|
||||
public String getRidBySheetId(String sheetId) {
|
||||
public Integer getRidBySheetId(int sheetId) {
|
||||
return ID_RID_MAP.get(sheetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据sheet name获取rid
|
||||
* 根据sheetId获取rid,从0开始
|
||||
*
|
||||
* @param sheetId Sheet的ID,从0开始
|
||||
* @return rid,从0开始
|
||||
* @since 5.5.5
|
||||
*/
|
||||
public Integer getRidBySheetIdBase0(int sheetId) {
|
||||
final Integer rid = getRidBySheetId(sheetId + 1);
|
||||
if (null != rid) {
|
||||
return rid - 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据sheet name获取rid,从1开始
|
||||
*
|
||||
* @param sheetName Sheet的name
|
||||
* @return rid
|
||||
* @return rid,从1开始
|
||||
*/
|
||||
public String getRidByName(String sheetName) {
|
||||
public Integer getRidByName(String sheetName) {
|
||||
return NAME_RID_MAP.get(sheetName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据sheet name获取rid,从0开始
|
||||
*
|
||||
* @param sheetName Sheet的name
|
||||
* @return rid,从0开始
|
||||
* @since 5.5.5
|
||||
*/
|
||||
public Integer getRidByNameBase0(String sheetName) {
|
||||
final Integer rid = getRidByName(sheetName);
|
||||
if (null != rid) {
|
||||
return rid - 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过sheet的序号获取rid
|
||||
*
|
||||
* @param index 序号,从0开始
|
||||
* @return rid
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public Integer getRidByIndex(int index) {
|
||||
return CollUtil.get(this.NAME_RID_MAP.values(), index);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过sheet的序号获取rid
|
||||
*
|
||||
* @param index 序号,从0开始
|
||||
* @return rid,从0开始
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public Integer getRidByIndexBase0(int index) {
|
||||
final Integer rid = CollUtil.get(this.NAME_RID_MAP.values(), index);
|
||||
if (null != rid) {
|
||||
return rid - 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
if (TAG_NAME.equalsIgnoreCase(localName)) {
|
||||
final int length = attributes.getLength();
|
||||
String sheetId = null;
|
||||
String rid = null;
|
||||
String name = null;
|
||||
for (int i = 0; i < length; i++) {
|
||||
switch (attributes.getLocalName(i)) {
|
||||
case SHEET_ID_ATTR:
|
||||
sheetId = attributes.getValue(i);
|
||||
break;
|
||||
case RID_ATTR:
|
||||
rid = attributes.getValue(i);
|
||||
break;
|
||||
case NAME_ATTR:
|
||||
name = attributes.getValue(i);
|
||||
break;
|
||||
}
|
||||
if (StrUtil.isNotEmpty(sheetId)) {
|
||||
ID_RID_MAP.put(sheetId, rid);
|
||||
final String ridStr = attributes.getValue(RID_ATTR);
|
||||
if (StrUtil.isEmpty(ridStr)) {
|
||||
return;
|
||||
}
|
||||
final int rid = Integer.parseInt(StrUtil.removePrefixIgnoreCase(ridStr, Excel07SaxReader.RID_PREFIX));
|
||||
|
||||
// sheet名和rid映射
|
||||
final String name = attributes.getValue(NAME_ATTR);
|
||||
if (StrUtil.isNotEmpty(name)) {
|
||||
NAME_RID_MAP.put(name, rid);
|
||||
}
|
||||
|
||||
// sheetId和rid映射
|
||||
final String sheetIdStr = attributes.getValue(SHEET_ID_ATTR);
|
||||
if (StrUtil.isNotEmpty(sheetIdStr)) {
|
||||
ID_RID_MAP.put(Integer.parseInt(sheetIdStr), rid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.IterUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
||||
import org.apache.poi.xwpf.usermodel.XWPFTable;
|
||||
@ -33,7 +34,7 @@ public class TableUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表格并填充数据
|
||||
* 创建表格并填充数据,默认表格
|
||||
*
|
||||
* @param doc {@link XWPFDocument}
|
||||
* @param data 数据
|
||||
@ -41,17 +42,33 @@ public class TableUtil {
|
||||
*/
|
||||
public static XWPFTable createTable(XWPFDocument doc, Iterable<?> data) {
|
||||
Assert.notNull(doc, "XWPFDocument must be not null !");
|
||||
XWPFTable table = doc.createTable();
|
||||
final XWPFTable table = doc.createTable();
|
||||
// 新建table的时候默认会新建一行,此处移除之
|
||||
table.removeRow(0);
|
||||
return writeTable(table, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为table填充数据
|
||||
*
|
||||
* @param table {@link XWPFTable}
|
||||
* @param data 数据
|
||||
* @return {@link XWPFTable}
|
||||
* @since 5.5.6
|
||||
*/
|
||||
public static XWPFTable writeTable(XWPFTable table, Iterable<?> data){
|
||||
Assert.notNull(table, "XWPFTable must be not null !");
|
||||
if (IterUtil.isEmpty(data)) {
|
||||
// 数据为空,返回空表
|
||||
return table;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
boolean isFirst = true;
|
||||
for (Object rowData : data) {
|
||||
writeRow(getOrCreateRow(table, index), rowData, true);
|
||||
index ++;
|
||||
writeRow(table.createRow(), rowData, isFirst);
|
||||
if(isFirst){
|
||||
isFirst = false;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
|
@ -58,9 +58,19 @@ public class ExcelSaxReadTest {
|
||||
|
||||
@Test
|
||||
public void readBySaxTest() {
|
||||
ExcelUtil.readBySax("blankAndDateTest.xlsx", "0", createRowHandler());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readBySaxByRidTest() {
|
||||
ExcelUtil.readBySax("blankAndDateTest.xlsx", 0, createRowHandler());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readBySaxByNameTest() {
|
||||
ExcelUtil.readBySax("blankAndDateTest.xlsx", "Sheet1", createRowHandler());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void readBySaxTest2() {
|
||||
|
@ -1,14 +1,15 @@
|
||||
package cn.hutool.poi.word;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.poi.word.Word07Writer;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ -49,4 +50,36 @@ public class WordWriterTest {
|
||||
writer.addTable(CollUtil.newArrayList(map));
|
||||
writer.flush(FileUtil.file("d:/test/test.docx"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void writeMapAsTableTest() {
|
||||
Word07Writer writer = new Word07Writer();
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("姓名", "张三");
|
||||
data.put("年龄", 23);
|
||||
data.put("成绩", 80.5);
|
||||
data.put("是否合格", true);
|
||||
data.put("考试日期", DateUtil.date());
|
||||
|
||||
Map<String, Object> data2 = new LinkedHashMap<>();
|
||||
data2.put("姓名", "李四");
|
||||
data2.put("年龄", 4);
|
||||
data2.put("成绩", 59);
|
||||
data2.put("是否合格", false);
|
||||
data2.put("考试日期", DateUtil.date());
|
||||
|
||||
ArrayList<Map<String, Object>> mapArrayList = CollUtil.newArrayList(data, data2);
|
||||
|
||||
// 添加段落(标题)
|
||||
writer.addText(new Font("方正小标宋简体", Font.PLAIN, 22), "我是第一部分");
|
||||
// 添加段落(正文)
|
||||
writer.addText(new Font("宋体", Font.PLAIN, 13), "我是正文第一部分");
|
||||
writer.addTable(mapArrayList);
|
||||
// 写出到文件
|
||||
writer.flush(FileUtil.file("d:/test/a.docx"));
|
||||
// 关闭
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-script</artifactId>
|
||||
@ -18,7 +18,7 @@
|
||||
<properties>
|
||||
<jython.version>2.7.2</jython.version>
|
||||
<luaj.version>3.0.1</luaj.version>
|
||||
<groovy.version>3.0.6</groovy.version>
|
||||
<groovy.version>3.0.7</groovy.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
@ -519,11 +519,25 @@ public class Setting extends AbsSetting implements Map<String, String> {
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 此key之前存在的值,如果没有返回null
|
||||
* @deprecated 此方法与getXXX参数顺序不一致容易造成问题,建议使用{@link #putByGroup(String, String, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public String put(String group, String key, String value) {
|
||||
return this.groupedMap.put(group, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将键值对加入到对应分组中
|
||||
*
|
||||
* @param key 键
|
||||
* @param group 分组
|
||||
* @param value 值
|
||||
* @return 此key之前存在的值,如果没有返回null
|
||||
*/
|
||||
public String putByGroup(String key, String group, String value) {
|
||||
return this.groupedMap.put(group, key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定分组中删除指定值
|
||||
*
|
||||
@ -622,12 +636,29 @@ public class Setting extends AbsSetting implements Map<String, String> {
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return 此key之前存在的值,如果没有返回null
|
||||
* @deprecated 此方法与getXXX参数顺序不一致容易引起,请使用{@link #setByGroup(String, String, String)}
|
||||
*/
|
||||
@Deprecated
|
||||
public Setting set(String group, String key, String value) {
|
||||
this.put(group, key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将键值对加入到对应分组中<br>
|
||||
* 此方法用于与getXXX统一参数顺序
|
||||
*
|
||||
* @param key 键
|
||||
* @param group 分组
|
||||
* @param value 值
|
||||
* @return 此key之前存在的值,如果没有返回null
|
||||
* @since 5.5.7
|
||||
*/
|
||||
public Setting setByGroup(String key, String group, String value) {
|
||||
this.putByGroup(key, group, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
// ------------------------------------------------- Override Map interface
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
|
@ -40,9 +40,9 @@ public class PropsUtil {
|
||||
|
||||
/**
|
||||
* 获取给定路径找到的第一个配置文件<br>
|
||||
* * name可以为不包括扩展名的文件名(默认.setting为结尾),也可以是文件名全称
|
||||
* * name可以为不包括扩展名的文件名(默认.properties为结尾),也可以是文件名全称
|
||||
*
|
||||
* @param names 文件名,如果没有扩展名,默认为.setting
|
||||
* @param names 文件名,如果没有扩展名,默认为.properties
|
||||
*
|
||||
* @return 当前环境下配置文件
|
||||
*/
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-system</artifactId>
|
||||
@ -26,7 +26,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>5.3.6</version>
|
||||
<version>5.3.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
2
pom.xml
2
pom.xml
@ -8,7 +8,7 @@
|
||||
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.5.5-SNAPSHOT</version>
|
||||
<version>5.5.8-SNAPSHOT</version>
|
||||
<name>hutool</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
Loading…
x
Reference in New Issue
Block a user