Merge remote-tracking branch 'upstream/v5-dev' into v5-dev

This commit is contained in:
lzpeng723
2021-01-09 19:01:38 +08:00
93 changed files with 1590 additions and 449 deletions

View File

@@ -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>

View File

@@ -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);

View File

@@ -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);

View File

@@ -1,28 +1,28 @@
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>
* ByteBuffer的相关介绍见https://www.cnblogs.com/ruber/p/6857159.html
*
*
* @author tanyaowu, looly
* @since 4.0.0
*
*/
public class BufferUtil {
/**
* 拷贝到一个新的ByteBuffer
*
* @param src 源ByteBuffer
*
* @param src 源ByteBuffer
* @param start 起始位置(包括)
* @param end 结束位置(不包括)
* @param end 结束位置(不包括)
* @return 新的ByteBuffer
*/
public static ByteBuffer copy(ByteBuffer src, int start, int end) {
@@ -31,8 +31,8 @@ public class BufferUtil {
/**
* 拷贝ByteBuffer
*
* @param src 源ByteBuffer
*
* @param src 源ByteBuffer
* @param dest 目标ByteBuffer
* @return 目标ByteBuffer
*/
@@ -42,9 +42,9 @@ public class BufferUtil {
/**
* 拷贝ByteBuffer
*
* @param src 源ByteBuffer
* @param dest 目标ByteBuffer
*
* @param src 源ByteBuffer
* @param dest 目标ByteBuffer
* @param length 长度
* @return 目标ByteBuffer
*/
@@ -54,12 +54,12 @@ public class BufferUtil {
/**
* 拷贝ByteBuffer
*
* @param src 源ByteBuffer
* @param srcStart 源开始的位置
* @param dest 目标ByteBuffer
*
* @param src 源ByteBuffer
* @param srcStart 源开始的位置
* @param dest 目标ByteBuffer
* @param destStart 目标开始的位置
* @param length 长度
* @param length 长度
* @return 目标ByteBuffer
*/
public static ByteBuffer copy(ByteBuffer src, int srcStart, ByteBuffer dest, int destStart, int length) {
@@ -69,7 +69,7 @@ public class BufferUtil {
/**
* 读取剩余部分并转为UTF-8编码字符串
*
*
* @param buffer ByteBuffer
* @return 字符串
* @since 4.5.0
@@ -80,8 +80,8 @@ public class BufferUtil {
/**
* 读取剩余部分并转为字符串
*
* @param buffer ByteBuffer
*
* @param buffer ByteBuffer
* @param charset 编码
* @return 字符串
* @since 4.5.0
@@ -92,7 +92,7 @@ public class BufferUtil {
/**
* 读取剩余部分bytes<br>
*
*
* @param buffer ByteBuffer
* @return bytes
*/
@@ -106,8 +106,8 @@ public class BufferUtil {
/**
* 读取指定长度的bytes<br>
* 如果长度不足则读取剩余部分此时buffer必须为读模式
*
* @param buffer ByteBuffer
*
* @param buffer ByteBuffer
* @param maxLength 最大长度
* @return bytes
*/
@@ -123,10 +123,10 @@ public class BufferUtil {
/**
* 读取指定区间的数据
*
*
* @param buffer {@link ByteBuffer}
* @param start 开始位置
* @param end 结束位置
* @param start 开始位置
* @param end 结束位置
* @return bytes
*/
public static byte[] readBytes(ByteBuffer buffer, int start, int end) {
@@ -148,13 +148,13 @@ public class BufferUtil {
/**
* 一行的末尾位置查找位置时位移ByteBuffer到结束位置<br>
* 支持的换行符如下:
*
*
* <pre>
* 1. \r\n
* 2. \n
* </pre>
*
* @param buffer {@link ByteBuffer}
* @param buffer {@link ByteBuffer}
* @param maxLength 读取最大长度
* @return 末尾位置,未找到或达到最大长度返回-1
*/
@@ -191,13 +191,13 @@ public class BufferUtil {
/**
* 读取一行如果buffer中最后一部分并非完整一行则返回null<br>
* 支持的换行符如下:
*
*
* <pre>
* 1. \r\n
* 2. \n
* </pre>
*
* @param buffer ByteBuffer
*
* @param buffer ByteBuffer
* @param charset 编码
* @return 一行
*/
@@ -217,7 +217,7 @@ public class BufferUtil {
/**
* 创建新Buffer
*
*
* @param data 数据
* @return {@link ByteBuffer}
* @since 4.5.0
@@ -228,8 +228,8 @@ public class BufferUtil {
/**
* 从字符串创建新Buffer
*
* @param data 数据
*
* @param data 数据
* @param charset 编码
* @return {@link ByteBuffer}
* @since 4.5.0
@@ -237,10 +237,10 @@ public class BufferUtil {
public static ByteBuffer create(CharSequence data, Charset charset) {
return create(StrUtil.bytes(data, charset));
}
/**
* 从字符串创建新Buffer使用UTF-8编码
*
*
* @param data 数据
* @return {@link ByteBuffer}
* @since 4.5.0
@@ -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);
}
}

View File

@@ -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;
}
/**

View File

@@ -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 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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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位身份证号码
*/

View File

@@ -0,0 +1,26 @@
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 {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
}

View File

@@ -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) {

View File

@@ -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();

View File

@@ -1,26 +1,34 @@
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 = '#';
/**
* 设置字段分隔符,默认逗号','
*
*
* @param fieldSeparator 字段分隔符,默认逗号','
*/
public void setFieldSeparator(final char fieldSeparator) {
@@ -29,10 +37,20 @@ public class CsvConfig implements Serializable{
/**
* 设置 文本分隔符,文本包装符,默认双引号'"'
*
*
* @param textDelimiter 文本分隔符,文本包装符,默认双引号'"'
*/
public void setTextDelimiter(char textDelimiter) {
this.textDelimiter = textDelimiter;
}
/**
* 设置 注释符号,用于区分注释行
*
* @param commentCharacter 注释符号,用于区分注释行
* @since 5.5.7
*/
public void setCommentCharacter(char commentCharacter) {
this.commentCharacter = commentCharacter;
}
}

View File

@@ -31,34 +31,44 @@ 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;
/**
* CSV解析器
*
*
* @param reader Reader
* @param config 配置null则为默认配置
*/
@@ -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;
}
@@ -135,66 +145,79 @@ public final class CsvParser implements Closeable, Serializable {
/**
* 当前行做为标题行
*
*
* @param currentFields 当前行字段列表
*/
private void initHeader(final List<String> currentFields) {
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);
}
}
header = new CsvRow(this.lineNo, Collections.unmodifiableMap(localHeaderMap), Collections.unmodifiableList(currentFields));
header = new CsvRow(this.lineNo, Collections.unmodifiableMap(localHeaderMap), Collections.unmodifiableList(currentFields));
}
/**
* 读取一行数据
*
*
* @return 一行数据
* @throws IORuntimeException IO异常
*/
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);
// 此处无需markread方法会重置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,61 +225,161 @@ 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;
}
@Override
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);
}
}
}

View File

@@ -29,17 +29,24 @@ 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
*
*
* @param filePath File CSV文件路径
*/
public CsvWriter(String filePath) {
@@ -48,7 +55,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造覆盖已有文件如果存在默认编码UTF-8
*
*
* @param file File CSV文件
*/
public CsvWriter(File file) {
@@ -57,9 +64,9 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造,覆盖已有文件(如果存在)
*
*
* @param filePath File CSV文件路径
* @param charset 编码
* @param charset 编码
*/
public CsvWriter(String filePath, Charset charset) {
this(FileUtil.file(filePath), charset);
@@ -67,8 +74,8 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造,覆盖已有文件(如果存在)
*
* @param file File CSV文件
*
* @param file File CSV文件
* @param charset 编码
*/
public CsvWriter(File file, Charset charset) {
@@ -77,9 +84,9 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造
*
*
* @param filePath File CSV文件路径
* @param charset 编码
* @param charset 编码
* @param isAppend 是否追加
*/
public CsvWriter(String filePath, Charset charset, boolean isAppend) {
@@ -88,9 +95,9 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造
*
* @param file CSV文件
* @param charset 编码
*
* @param file CSV文件
* @param charset 编码
* @param isAppend 是否追加
*/
public CsvWriter(File file, Charset charset, boolean isAppend) {
@@ -99,11 +106,11 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造
*
*
* @param filePath CSV文件路径
* @param charset 编码
* @param charset 编码
* @param isAppend 是否追加
* @param config 写出配置null则使用默认配置
* @param config 写出配置null则使用默认配置
*/
public CsvWriter(String filePath, Charset charset, boolean isAppend, CsvWriteConfig config) {
this(FileUtil.file(filePath), charset, isAppend, config);
@@ -111,11 +118,11 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造
*
* @param file CSV文件
* @param charset 编码
*
* @param file CSV文件
* @param charset 编码
* @param isAppend 是否追加
* @param config 写出配置null则使用默认配置
* @param config 写出配置null则使用默认配置
*/
public CsvWriter(File file, Charset charset, boolean isAppend, CsvWriteConfig config) {
this(FileUtil.getWriter(file, charset, isAppend), config);
@@ -123,7 +130,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造,使用默认配置
*
*
* @param writer {@link Writer}
*/
public CsvWriter(Writer writer) {
@@ -132,7 +139,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 构造
*
*
* @param writer Writer
* @param config 写出配置null则使用默认配置
*/
@@ -144,7 +151,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 设置是否始终使用文本分隔符文本包装符默认false按需添加
*
*
* @param alwaysDelimitText 是否始终使用文本分隔符文本包装符默认false按需添加
* @return this
*/
@@ -155,7 +162,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 设置换行符
*
*
* @param lineDelimiter 换行符
* @return this
*/
@@ -166,7 +173,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 将多行写出到Writer
*
*
* @param lines 多行数据
* @return this
* @throws IORuntimeException IO异常
@@ -183,7 +190,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
/**
* 将多行写出到Writer
*
*
* @param lines 多行数据,每行数据可以是集合或者数组
* @return this
* @throws IORuntimeException IO异常
@@ -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;
}

View File

@@ -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[]) {

View File

@@ -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 = '@';
/**

View File

@@ -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:

View File

@@ -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);
}
/**

View File

@@ -298,7 +298,7 @@ public class RuntimeUtil {
*
* @return 最大可用内存
*/
public final long getUsableMemory() {
public static long getUsableMemory() {
return getMaxMemory() - getTotalMemory() + getFreeMemory();
}
}

View File

@@ -644,7 +644,7 @@ public class ZipUtil {
outItemFile.mkdirs();
} else {
// 文件
FileUtil.writeFromStream(zipStream, outItemFile);
FileUtil.writeFromStream(zipStream, outItemFile, false);
}
});
return outFile;

View File

@@ -1,62 +1,95 @@
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", "李华");
bean.set("age", 12);
String name = bean.get("name");
Assert.assertEquals(user.getName(), name);
int age = bean.get("age");
Assert.assertEquals(user.getAge(), age);
//重复包装测试
DynaBean bean2 = new DynaBean(bean);
User user2 = bean2.getBean();
Assert.assertEquals(user, user2);
//执行指定方法
Object invoke = bean2.invoke("testMethod");
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 + "]";
}
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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,26 +17,55 @@ 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"},
new String[] {"a3", "b3", "c3"}
);
}
@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);
}
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -1 +1,2 @@
"sss,sss",姓名,"性别",关注"对象",年龄
# 这是一行注释,读取时应忽略
"sss,sss",姓名,"性别",关注"对象",年龄,"","""
Can't render this file because it contains an unexpected character in line 1 and column 33.