From 0d525621803acda59eec03ecdcc81de810ca74ff Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 26 Oct 2022 21:20:32 +0800 Subject: [PATCH] fix code --- .../hutool/core/collection/iter/LineIter.java | 6 +- .../java/cn/hutool/core/compress/ZipUtil.java | 6 +- .../hutool/core/exceptions/ExceptionUtil.java | 2 +- .../java/cn/hutool/core/io/BomReader.java | 6 +- .../java/cn/hutool/core/io/FileTypeUtil.java | 24 +- .../main/java/cn/hutool/core/io/FileUtil.java | 42 +- .../main/java/cn/hutool/core/io/IoUtil.java | 441 +++++------------- .../main/java/cn/hutool/core/io/NioUtil.java | 69 +-- .../java/cn/hutool/core/io/SerializeUtil.java | 3 +- .../hutool/core/io/checksum/ChecksumUtil.java | 101 ++++ .../cn/hutool/core/io/copy/ChannelCopier.java | 4 - .../core/io/copy/FileChannelCopier.java | 129 +++++ .../cn/hutool/core/io/copy/StreamCopier.java | 4 +- .../cn/hutool/core/io/file/FileReader.java | 2 +- .../java/cn/hutool/core/io/file/PathUtil.java | 2 +- .../io/resource/CharSequenceResource.java | 2 +- .../core/io/resource/FileObjectResource.java | 2 +- .../core/io/resource/MultiFileResource.java | 28 +- .../cn/hutool/core/io/resource/Resource.java | 2 +- .../core/io/{ => stream}/BOMInputStream.java | 3 +- .../io/{ => stream}/EmptyInputStream.java | 2 +- .../EmptyOutputStream.java} | 13 +- .../FastByteArrayOutputStream.java | 4 +- .../hutool/core/io/stream/StrInputStream.java | 47 ++ .../hutool/core/io/stream/StreamReader.java | 160 +++++++ .../hutool/core/io/stream/StreamWriter.java | 115 +++++ .../ValidateObjectInputStream.java | 2 +- .../hutool/core/io/stream/package-info.java | 6 + .../MultipartRequestInputStream.java | 2 +- .../java/cn/hutool/core/net/url/URLUtil.java | 2 +- .../cn/hutool/core/text/CharSequenceUtil.java | 2 +- .../cn/hutool/core/io/FileTypeUtilTest.java | 38 +- .../java/cn/hutool/core/io/IoUtilTest.java | 121 +++++ .../java/cn/hutool/core/io/NioUtilTest.java | 24 + .../main/java/cn/hutool/crypto/ASN1Util.java | 2 +- .../main/java/cn/hutool/crypto/PemUtil.java | 4 +- .../crypto/asymmetric/AsymmetricCrypto.java | 2 +- .../asymmetric/AsymmetricEncryptor.java | 12 - .../crypto/symmetric/SymmetricEncryptor.java | 33 -- .../engine/freemarker/FreemarkerTemplate.java | 2 +- .../engine/thymeleaf/ThymeleafTemplate.java | 2 +- .../engine/velocity/VelocityTemplate.java | 2 +- .../cn/hutool/http/client/HttpDownloader.java | 2 +- .../cn/hutool/http/client/body/FormBody.java | 120 +++++ .../http/client/body/MultipartBody.java | 51 +- .../hutool/http/client/body/RequestBody.java | 2 +- ...codedBody.java => UrlEncodedFormBody.java} | 17 +- .../http/client/engine/jdk/HttpRequest.java | 4 +- .../http/client/engine/jdk/HttpResponse.java | 2 +- .../client/engine/okhttp/OkHttpResponse.java | 2 +- .../main/java/cn/hutool/json/JSONTokener.java | 2 +- .../java/cn/hutool/setting/GroupedSet.java | 2 +- .../java/cn/hutool/setting/SettingLoader.java | 2 +- .../java/cn/hutool/setting/yaml/YamlUtil.java | 2 +- 54 files changed, 1119 insertions(+), 564 deletions(-) create mode 100755 hutool-core/src/main/java/cn/hutool/core/io/checksum/ChecksumUtil.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/io/copy/FileChannelCopier.java rename hutool-core/src/main/java/cn/hutool/core/io/{ => stream}/BOMInputStream.java (97%) rename hutool-core/src/main/java/cn/hutool/core/io/{ => stream}/EmptyInputStream.java (96%) rename hutool-core/src/main/java/cn/hutool/core/io/{NullOutputStream.java => stream/EmptyOutputStream.java} (72%) rename hutool-core/src/main/java/cn/hutool/core/io/{ => stream}/FastByteArrayOutputStream.java (95%) create mode 100755 hutool-core/src/main/java/cn/hutool/core/io/stream/StrInputStream.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/io/stream/StreamReader.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/io/stream/StreamWriter.java rename hutool-core/src/main/java/cn/hutool/core/io/{ => stream}/ValidateObjectInputStream.java (98%) create mode 100755 hutool-core/src/main/java/cn/hutool/core/io/stream/package-info.java create mode 100755 hutool-core/src/test/java/cn/hutool/core/io/NioUtilTest.java create mode 100755 hutool-http/src/main/java/cn/hutool/http/client/body/FormBody.java rename hutool-http/src/main/java/cn/hutool/http/client/body/{FormUrlEncodedBody.java => UrlEncodedFormBody.java} (51%) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/iter/LineIter.java b/hutool-core/src/main/java/cn/hutool/core/collection/iter/LineIter.java index 1aca82d92..c22c4cce7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/iter/LineIter.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/iter/LineIter.java @@ -1,7 +1,5 @@ package cn.hutool.core.collection.iter; -import cn.hutool.core.collection.iter.ComputeIter; -import cn.hutool.core.collection.iter.IterableIter; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; @@ -49,7 +47,7 @@ public class LineIter extends ComputeIter implements IterableIter implements IterableIter * 原理是首先获取ClassPath路径,由于在web项目中ClassPath位于 WEB-INF/classes/下,故向上获取两级目录即可。 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index ac23975a7..95909aec2 100755 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -1,15 +1,18 @@ package cn.hutool.core.io; +import cn.hutool.core.codec.HexUtil; import cn.hutool.core.collection.iter.LineIter; -import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.copy.FileChannelCopier; import cn.hutool.core.io.copy.ReaderWriterCopier; import cn.hutool.core.io.copy.StreamCopier; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; +import cn.hutool.core.io.stream.StreamReader; +import cn.hutool.core.io.stream.StreamWriter; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.func.SerConsumer; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.codec.HexUtil; import cn.hutool.core.text.StrUtil; +import cn.hutool.core.util.CharsetUtil; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -26,23 +29,17 @@ import java.io.Flushable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PushbackInputStream; import java.io.PushbackReader; import java.io.Reader; -import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.nio.CharBuffer; -import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.util.Collection; import java.util.Objects; -import java.util.zip.CRC32; -import java.util.zip.CheckedInputStream; -import java.util.zip.Checksum; /** * IO工具类
@@ -52,7 +49,7 @@ import java.util.zip.Checksum; */ public class IoUtil extends NioUtil { - // -------------------------------------------------------------------------------------- Copy start + // region -------------------------------------------------------------------------------------- Copy /** * 将Reader中的内容复制到Writer中 使用默认缓存大小,拷贝后不关闭Reader @@ -96,15 +93,17 @@ public class IoUtil extends NioUtil { /** * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader * - * @param reader Reader - * @param writer Writer - * @param bufferSize 缓存大小 - * @param count 最大长度 - * @param streamProgress 进度处理器 + * @param reader Reader,非空 + * @param writer Writer,非空 + * @param bufferSize 缓存大小,-1表示默认 + * @param count 最大长度,-1表示无限制 + * @param streamProgress 进度处理器,{@code null}表示无 * @return 传输的byte数 * @throws IORuntimeException IO异常 */ public static long copy(final Reader reader, final Writer writer, final int bufferSize, final long count, final StreamProgress streamProgress) throws IORuntimeException { + Assert.notNull(reader, "Reader is null !"); + Assert.notNull(writer, "Writer is null !"); return new ReaderWriterCopier(bufferSize, count, streamProgress).copy(reader, writer); } @@ -153,13 +152,15 @@ public class IoUtil extends NioUtil { * @param in 输入流 * @param out 输出流 * @param bufferSize 缓存大小 - * @param count 总拷贝长度 + * @param count 总拷贝长度,-1表示无限制 * @param streamProgress 进度条 * @return 传输的byte数 * @throws IORuntimeException IO异常 * @since 5.7.8 */ public static long copy(final InputStream in, final OutputStream out, final int bufferSize, final long count, final StreamProgress streamProgress) throws IORuntimeException { + Assert.notNull(in, "InputStream is null !"); + Assert.notNull(out, "OutputStream is null !"); return new StreamCopier(bufferSize, count, streamProgress).copy(in, out); } @@ -175,42 +176,22 @@ public class IoUtil extends NioUtil { Assert.notNull(in, "FileInputStream is null!"); Assert.notNull(out, "FileOutputStream is null!"); - FileChannel inChannel = null; - FileChannel outChannel = null; - try { - inChannel = in.getChannel(); - outChannel = out.getChannel(); - return copy(inChannel, outChannel); - } finally { - close(outChannel); - close(inChannel); - } + return FileChannelCopier.of().copy(in, out); } - // -------------------------------------------------------------------------------------- Copy end + // endregion -------------------------------------------------------------------------------------- Copy - // -------------------------------------------------------------------------------------- getReader and getWriter start + // region -------------------------------------------------------------------------------------- toReader and toWriter /** - * 获得一个文件读取器,默认使用UTF-8编码 + * 获得一个文件读取器,默认使用 UTF-8 编码 * * @param in 输入流 * @return BufferedReader对象 * @since 5.1.6 */ - public static BufferedReader getUtf8Reader(final InputStream in) { - return getReader(in, CharsetUtil.UTF_8); - } - - /** - * 从{@link BOMInputStream}中获取Reader - * - * @param in {@link BOMInputStream} - * @return {@link BufferedReader} - * @since 5.5.8 - */ - public static BufferedReader getReader(final BOMInputStream in) { - return getReader(in, CharsetUtil.charset(in.getCharset())); + public static BufferedReader toUtf8Reader(final InputStream in) { + return toReader(in, CharsetUtil.UTF_8); } /** @@ -220,7 +201,7 @@ public class IoUtil extends NioUtil { * @return {@link BomReader} * @since 5.7.14 */ - public static BomReader getBomReader(final InputStream in) { + public static BomReader toBomReader(final InputStream in) { return new BomReader(in); } @@ -231,7 +212,7 @@ public class IoUtil extends NioUtil { * @param charset 字符集 * @return BufferedReader对象 */ - public static BufferedReader getReader(final InputStream in, final Charset charset) { + public static BufferedReader toReader(final InputStream in, final Charset charset) { if (null == in) { return null; } @@ -246,35 +227,6 @@ public class IoUtil extends NioUtil { return new BufferedReader(reader); } - /** - * 获得{@link BufferedReader}
- * 如果是{@link BufferedReader}强转返回,否则新建。如果提供的Reader为null返回null - * - * @param reader 普通Reader,如果为null返回null - * @return {@link BufferedReader} or null - * @since 3.0.9 - */ - public static BufferedReader getReader(final Reader reader) { - if (null == reader) { - return null; - } - - return (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader); - } - - /** - * 获得{@link PushbackReader}
- * 如果是{@link PushbackReader}强转返回,否则新建 - * - * @param reader 普通Reader - * @param pushBackSize 推后的byte数 - * @return {@link PushbackReader} - * @since 3.1.0 - */ - public static PushbackReader getPushBackReader(final Reader reader, final int pushBackSize) { - return (reader instanceof PushbackReader) ? (PushbackReader) reader : new PushbackReader(reader, pushBackSize); - } - /** * 获得一个Writer,默认编码UTF-8 * @@ -282,8 +234,8 @@ public class IoUtil extends NioUtil { * @return OutputStreamWriter对象 * @since 5.1.6 */ - public static OutputStreamWriter getUtf8Writer(final OutputStream out) { - return getWriter(out, CharsetUtil.UTF_8); + public static OutputStreamWriter toUtf8Writer(final OutputStream out) { + return toWriter(out, CharsetUtil.UTF_8); } /** @@ -293,7 +245,7 @@ public class IoUtil extends NioUtil { * @param charset 字符集 * @return OutputStreamWriter对象 */ - public static OutputStreamWriter getWriter(final OutputStream out, final Charset charset) { + public static OutputStreamWriter toWriter(final OutputStream out, final Charset charset) { if (null == out) { return null; } @@ -304,9 +256,9 @@ public class IoUtil extends NioUtil { return new OutputStreamWriter(out, charset); } } - // -------------------------------------------------------------------------------------- getReader and getWriter end + // endregion -------------------------------------------------------------------------------------- toReader and toWriter - // -------------------------------------------------------------------------------------- read start + // region -------------------------------------------------------------------------------------- read /** * 从流中读取UTF8编码的内容 @@ -353,25 +305,7 @@ public class IoUtil extends NioUtil { * @since 5.5.3 */ public static FastByteArrayOutputStream read(final InputStream in, final boolean isClose) throws IORuntimeException { - final FastByteArrayOutputStream out; - if (in instanceof FileInputStream) { - // 文件流的长度是可预见的,此时直接读取效率更高 - try { - out = new FastByteArrayOutputStream(in.available()); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } else { - out = new FastByteArrayOutputStream(); - } - try { - copy(in, out); - } finally { - if (isClose) { - close(in); - } - } - return out; + return StreamReader.of(in, isClose).read(); } /** @@ -431,28 +365,7 @@ public class IoUtil extends NioUtil { * @since 5.0.4 */ public static byte[] readBytes(final InputStream in, final boolean isClose) throws IORuntimeException { - if (in instanceof FileInputStream) { - // 文件流的长度是可预见的,此时直接读取效率更高 - final byte[] result; - try { - final int available = in.available(); - result = new byte[available]; - final int readLength = in.read(result); - if (readLength != available) { - throw new IOException(StrUtil.format("File length is [{}] but read [{}]!", available, readLength)); - } - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - if (isClose) { - close(in); - } - } - return result; - } - - // 未知bytes总量的流 - return read(in, isClose).toByteArray(); + return StreamReader.of(in, isClose).readBytes(); } /** @@ -464,16 +377,7 @@ public class IoUtil extends NioUtil { * @throws IORuntimeException IO异常 */ public static byte[] readBytes(final InputStream in, final int length) throws IORuntimeException { - if (null == in) { - return null; - } - if (length <= 0) { - return new byte[0]; - } - - final FastByteArrayOutputStream out = new FastByteArrayOutputStream(length); - copy(in, out, DEFAULT_BUFFER_SIZE, length, null); - return out.toByteArray(); + return StreamReader.of(in, false).readBytes(length); } /** @@ -489,45 +393,6 @@ public class IoUtil extends NioUtil { return HexUtil.encodeHexStr(readBytes(in, length), toLowerCase); } - /** - * 从流中读取前28个byte并转换为16进制,字母部分使用大写 - * - * @param in {@link InputStream} - * @return 16进制字符串 - * @throws IORuntimeException IO异常 - */ - public static String readHex28Upper(final InputStream in) throws IORuntimeException { - return readHex(in, 28, false); - } - - /** - * 从流中读取前28个byte并转换为16进制,字母部分使用小写 - * - * @param in {@link InputStream} - * @return 16进制字符串 - * @throws IORuntimeException IO异常 - */ - public static String readHex28Lower(final InputStream in) throws IORuntimeException { - return readHex(in, 28, true); - } - - /** - * 从流中读取对象,即对象的反序列化 - * - *

- * 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!! - *

- * - * @param 读取对象的类型 - * @param in 输入流 - * @return 输出流 - * @throws IORuntimeException IO异常 - * @throws UtilException ClassNotFoundException包装 - */ - public static T readObj(final InputStream in) throws IORuntimeException, UtilException { - return readObj(in, null); - } - /** * 从流中读取对象,即对象的反序列化,读取后不关闭流 * @@ -535,54 +400,15 @@ public class IoUtil extends NioUtil { * 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!! *

* - * @param 读取对象的类型 - * @param in 输入流 - * @param clazz 读取对象类型 + * @param 读取对象的类型 + * @param in 输入流 + * @param acceptClasses 读取对象类型 * @return 输出流 * @throws IORuntimeException IO异常 * @throws UtilException ClassNotFoundException包装 */ - public static T readObj(final InputStream in, final Class clazz) throws IORuntimeException, UtilException { - try { - return readObj((in instanceof ValidateObjectInputStream) ? - (ValidateObjectInputStream) in : new ValidateObjectInputStream(in), - clazz); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - - /** - * 从流中读取对象,即对象的反序列化,读取后不关闭流 - * - *

- * 此方法使用了{@link ValidateObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞
- * 通过构造{@link ValidateObjectInputStream},调用{@link ValidateObjectInputStream#accept(Class[])} - * 或者{@link ValidateObjectInputStream#refuse(Class[])}方法添加可以被序列化的类或者禁止序列化的类。 - *

- * - * @param 读取对象的类型 - * @param in 输入流,使用{@link ValidateObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞 - * @param clazz 读取对象类型 - * @return 输出流 - * @throws IORuntimeException IO异常 - * @throws UtilException ClassNotFoundException包装 - */ - @SuppressWarnings("unchecked") - public static T readObj(final ValidateObjectInputStream in, final Class clazz) throws IORuntimeException, UtilException { - if (in == null) { - throw new IllegalArgumentException("The InputStream must not be null"); - } - if(null != clazz){ - in.accept(clazz); - } - try { - return (T) in.readObject(); - } catch (final IOException e) { - throw new IORuntimeException(e); - } catch (final ClassNotFoundException e) { - throw new UtilException(e); - } + public static T readObj(final InputStream in, final Class... acceptClasses) throws IORuntimeException, UtilException { + return StreamReader.of(in, false).readObj(acceptClasses); } /** @@ -609,7 +435,7 @@ public class IoUtil extends NioUtil { * @throws IORuntimeException IO异常 */ public static > T readLines(final InputStream in, final Charset charset, final T collection) throws IORuntimeException { - return readLines(getReader(in, charset), collection); + return readLines(toReader(in, charset), collection); } /** @@ -648,7 +474,7 @@ public class IoUtil extends NioUtil { * @since 3.0.9 */ public static void readLines(final InputStream in, final Charset charset, final SerConsumer lineHandler) throws IORuntimeException { - readLines(getReader(in, charset), lineHandler); + readLines(toReader(in, charset), lineHandler); } /** @@ -669,7 +495,20 @@ public class IoUtil extends NioUtil { } } - // -------------------------------------------------------------------------------------- read end + // endregion -------------------------------------------------------------------------------------- read + + // region -------------------------------------------------------------------------------------- toStream + + /** + * String 转为UTF-8编码的字节流流 + * + * @param content 内容 + * @return 字节流 + * @since 4.5.1 + */ + public static ByteArrayInputStream toUtf8Stream(final String content) { + return toStream(content, CharsetUtil.UTF_8); + } /** * String 转为流 @@ -685,17 +524,6 @@ public class IoUtil extends NioUtil { return toStream(StrUtil.bytes(content, charset)); } - /** - * String 转为UTF-8编码的字节流流 - * - * @param content 内容 - * @return 字节流 - * @since 4.5.1 - */ - public static ByteArrayInputStream toUtf8Stream(final String content) { - return toStream(content, CharsetUtil.UTF_8); - } - /** * 文件转为{@link FileInputStream} * @@ -869,6 +697,19 @@ public class IoUtil extends NioUtil { return in; } + /** + * 获得{@link PushbackReader}
+ * 如果是{@link PushbackReader}强转返回,否则新建 + * + * @param reader 普通Reader + * @param pushBackSize 推后的byte数 + * @return {@link PushbackReader} + * @since 3.1.0 + */ + public static PushbackReader toPushBackReader(final Reader reader, final int pushBackSize) { + return (reader instanceof PushbackReader) ? (PushbackReader) reader : new PushbackReader(reader, pushBackSize); + } + /** * 转换为{@link PushbackInputStream}
* 如果传入的输入流已经是{@link PushbackInputStream},强转返回,否则新建一个 @@ -918,6 +759,31 @@ public class IoUtil extends NioUtil { return pushbackInputStream; } + // endregion -------------------------------------------------------------------------------------- toStream + + // region ----------------------------------------------------------------------------------------------- write + + /** + * 将byte[]写到流中,并关闭目标流 + * + * @param out 输出流 + * @param content 写入的内容 + * @throws IORuntimeException IO异常 + */ + public static void writeClose(final OutputStream out, final byte[] content) throws IORuntimeException { + write(out, true, content); + } + + /** + * 将byte[]写到流中,并关闭目标流 + * + * @param out 输出流 + * @param content 写入的内容 + * @throws IORuntimeException IO异常 + */ + public static void write(final OutputStream out, final byte[] content) throws IORuntimeException { + write(out, false, content); + } /** * 将byte[]写到流中 @@ -928,15 +794,7 @@ public class IoUtil extends NioUtil { * @throws IORuntimeException IO异常 */ public static void write(final OutputStream out, final boolean isCloseOut, final byte[] content) throws IORuntimeException { - try { - out.write(content); - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - if (isCloseOut) { - close(out); - } - } + StreamWriter.of(out, isCloseOut).write(content); } /** @@ -963,35 +821,7 @@ public class IoUtil extends NioUtil { * @since 3.0.9 */ public static void write(final OutputStream out, final Charset charset, final boolean isCloseOut, final Object... contents) throws IORuntimeException { - OutputStreamWriter osw = null; - try { - osw = getWriter(out, charset); - for (final Object content : contents) { - if (content != null) { - osw.write(Convert.toStr(content, StrUtil.EMPTY)); - } - } - osw.flush(); - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - if (isCloseOut) { - close(osw); - } - } - } - - /** - * 将多部分内容写到流中 - * - * @param out 输出流 - * @param isCloseOut 写入完毕是否关闭输出流 - * @param obj 写入的对象内容 - * @throws IORuntimeException IO异常 - * @since 5.3.3 - */ - public static void writeObj(final OutputStream out, final boolean isCloseOut, final Serializable obj) throws IORuntimeException { - writeObjects(out, isCloseOut, obj); + StreamWriter.of(out, isCloseOut).writeStr(charset, contents); } /** @@ -1002,24 +832,10 @@ public class IoUtil extends NioUtil { * @param contents 写入的内容 * @throws IORuntimeException IO异常 */ - public static void writeObjects(final OutputStream out, final boolean isCloseOut, final Serializable... contents) throws IORuntimeException { - ObjectOutputStream osw = null; - try { - osw = out instanceof ObjectOutputStream ? (ObjectOutputStream) out : new ObjectOutputStream(out); - for (final Object content : contents) { - if (content != null) { - osw.writeObject(content); - } - } - osw.flush(); - } catch (final IOException e) { - throw new IORuntimeException(e); - } finally { - if (isCloseOut) { - close(osw); - } - } + public static void writeObjects(final OutputStream out, final boolean isCloseOut, final Object... contents) throws IORuntimeException { + StreamWriter.of(out, isCloseOut).writeObj(contents); } + // endregion ----------------------------------------------------------------------------------------------- write /** * 从缓存中刷出数据 @@ -1060,7 +876,7 @@ public class IoUtil extends NioUtil { * @param obj 可关闭对象 * @since 4.3.2 */ - public static void closeIfPosible(final Object obj) { + public static void closeIfPossible(final Object obj) { if (obj instanceof AutoCloseable) { close((AutoCloseable) obj); } @@ -1112,8 +928,8 @@ public class IoUtil extends NioUtil { * @since 4.0.6 */ public static boolean contentEquals(Reader input1, Reader input2) throws IORuntimeException { - input1 = getReader(input1); - input2 = getReader(input2); + input1 = toBuffered(input1); + input2 = toBuffered(input2); try { int ch = input1.read(); @@ -1143,8 +959,8 @@ public class IoUtil extends NioUtil { * @since 4.0.6 */ public static boolean contentEqualsIgnoreEOL(final Reader input1, final Reader input2) throws IORuntimeException { - final BufferedReader br1 = getReader(input1); - final BufferedReader br2 = getReader(input2); + final BufferedReader br1 = toBuffered(input1); + final BufferedReader br2 = toBuffered(input2); try { String line1 = br1.readLine(); @@ -1159,54 +975,6 @@ public class IoUtil extends NioUtil { } } - /** - * 计算流CRC32校验码,计算后关闭流 - * - * @param in 文件,不能为目录 - * @return CRC32值 - * @throws IORuntimeException IO异常 - * @since 4.0.6 - */ - public static long checksumCRC32(final InputStream in) throws IORuntimeException { - return checksum(in, new CRC32()).getValue(); - } - - /** - * 计算流的校验码,计算后关闭流 - * - * @param in 流 - * @param checksum {@link Checksum} - * @return Checksum - * @throws IORuntimeException IO异常 - * @since 4.0.10 - */ - public static Checksum checksum(InputStream in, Checksum checksum) throws IORuntimeException { - Assert.notNull(in, "InputStream is null !"); - if (null == checksum) { - checksum = new CRC32(); - } - try { - in = new CheckedInputStream(in, checksum); - IoUtil.copy(in, new NullOutputStream()); - } finally { - IoUtil.close(in); - } - return checksum; - } - - /** - * 计算流的校验码,计算后关闭流 - * - * @param in 流 - * @param checksum {@link Checksum} - * @return Checksum - * @throws IORuntimeException IO异常 - * @since 5.4.0 - */ - public static long checksumValue(final InputStream in, final Checksum checksum) { - return checksum(in, checksum).getValue(); - } - /** * 返回行遍历器 *
@@ -1256,12 +1024,13 @@ public class IoUtil extends NioUtil {
 
 	/**
 	 * {@link ByteArrayOutputStream} 转换为String
-	 * @param out {@link ByteArrayOutputStream}
+	 *
+	 * @param out     {@link ByteArrayOutputStream}
 	 * @param charset 编码
 	 * @return 字符串
 	 * @since 5.7.17
 	 */
-	public static String toStr(final ByteArrayOutputStream out, final Charset charset){
+	public static String toStr(final ByteArrayOutputStream out, final Charset charset) {
 		try {
 			return out.toString(charset.name());
 		} catch (final UnsupportedEncodingException e) {
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
index 56cfa5868..b584444c6 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
@@ -1,6 +1,8 @@
 package cn.hutool.core.io;
 
 import cn.hutool.core.io.copy.ChannelCopier;
+import cn.hutool.core.io.copy.FileChannelCopier;
+import cn.hutool.core.io.stream.FastByteArrayOutputStream;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.CharsetUtil;
 import cn.hutool.core.text.StrUtil;
@@ -60,74 +62,35 @@ public class NioUtil {
 	 * 拷贝流
* 本方法不会关闭流 * - * @param in 输入流 - * @param out 输出流 - * @param bufferSize 缓存大小 - * @param count 最大长度 - * @param streamProgress 进度条 + * @param in 输入流, 非空 + * @param out 输出流, 非空 + * @param bufferSize 缓存大小,-1表示默认 + * @param count 最大长度,-1表示无限制 + * @param streamProgress 进度条,{@code null}表示无进度条 * @return 传输的byte数 * @throws IORuntimeException IO异常 * @since 5.7.8 */ public static long copyByNIO(final InputStream in, final OutputStream out, final int bufferSize, final long count, final StreamProgress streamProgress) throws IORuntimeException { + Assert.notNull(in, "InputStream channel is null!"); + Assert.notNull(out, "OutputStream channel is null!"); return copy(Channels.newChannel(in), Channels.newChannel(out), bufferSize, count, streamProgress); } /** * 拷贝文件Channel,使用NIO,拷贝后不会关闭channel * - * @param inChannel {@link FileChannel} - * @param outChannel {@link FileChannel} + * @param in {@link FileChannel},非空 + * @param out {@link FileChannel},非空 * @return 拷贝的字节数 * @throws IORuntimeException IO异常 * @since 5.5.3 */ - public static long copy(final FileChannel inChannel, final FileChannel outChannel) throws IORuntimeException { - Assert.notNull(inChannel, "In channel is null!"); - Assert.notNull(outChannel, "Out channel is null!"); + public static long copy(final FileChannel in, final FileChannel out) throws IORuntimeException { + Assert.notNull(in, "In channel is null!"); + Assert.notNull(out, "Out channel is null!"); - try { - return copySafely(inChannel, outChannel); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - - /** - * 文件拷贝实现 - * - *
-	 * FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
-	 * 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
-	 * 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
-	 *
-	 * // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
-	 * try (InputStream srcStream = zipFile.getInputStream(zipEntry);
-	 * 		ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
-	 * 		FileOutputStream fos = new FileOutputStream(saveFile);
-	 * 		FileChannel dstChannel = fos.getChannel()) {
-	 * 		dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
-	 *  }
-	 * 
- * - * @param inChannel 输入通道 - * @param outChannel 输出通道 - * @return 输入通道的字节数 - * @throws IOException 发生IO错误 - * @link http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/java/nio/FileChannelImpl.java - * @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java - * @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/FileChannelImpl.c - * @author z8g - * @since 5.7.21 - */ - private static long copySafely(final FileChannel inChannel, final FileChannel outChannel) throws IOException { - final long totalBytes = inChannel.size(); - for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失 - final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数 - pos += writeBytes; - remaining -= writeBytes; - } - return totalBytes; + return FileChannelCopier.of().copy(in, out); } /** @@ -184,6 +147,8 @@ public class NioUtil { * @since 5.7.8 */ public static long copy(final ReadableByteChannel in, final WritableByteChannel out, final int bufferSize, final long count, final StreamProgress streamProgress) throws IORuntimeException { + Assert.notNull(in, "In channel is null!"); + Assert.notNull(out, "Out channel is null!"); return new ChannelCopier(bufferSize, count, streamProgress).copy(in, out); } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/SerializeUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/SerializeUtil.java index 24ae3263d..1cf940bde 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/SerializeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/SerializeUtil.java @@ -1,8 +1,7 @@ package cn.hutool.core.io; import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.io.FastByteArrayOutputStream; -import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.io.Serializable; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/checksum/ChecksumUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/checksum/ChecksumUtil.java new file mode 100755 index 000000000..a96bc8f66 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/checksum/ChecksumUtil.java @@ -0,0 +1,101 @@ +package cn.hutool.core.io.checksum; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.stream.EmptyOutputStream; +import cn.hutool.core.lang.Assert; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.zip.CRC32; +import java.util.zip.CheckedInputStream; +import java.util.zip.Checksum; + +/** + * 校验码工具 + * + * @author looly + */ +public class ChecksumUtil { + + /** + * 计算文件CRC32校验码 + * + * @param file 文件,不能为目录 + * @return CRC32值 + * @throws IORuntimeException IO异常 + */ + public static long checksumCRC32(final File file) throws IORuntimeException { + return checksum(file, new CRC32()).getValue(); + } + + /** + * 计算流CRC32校验码,计算后关闭流 + * + * @param in 文件,不能为目录 + * @return CRC32值 + * @throws IORuntimeException IO异常 + * @since 4.0.6 + */ + public static long checksumCRC32(final InputStream in) throws IORuntimeException { + return checksum(in, new CRC32()).getValue(); + } + + /** + * 计算文件校验码 + * + * @param file 文件,不能为目录 + * @param checksum {@link Checksum} + * @return Checksum + * @throws IORuntimeException IO异常 + */ + public static Checksum checksum(final File file, final Checksum checksum) throws IORuntimeException { + Assert.notNull(file, "File is null !"); + if (file.isDirectory()) { + throw new IllegalArgumentException("Checksums can't be computed on directories"); + } + try { + return checksum(new FileInputStream(file), checksum); + } catch (final FileNotFoundException e) { + throw new IORuntimeException(e); + } + } + + /** + * 计算流的校验码,计算后关闭流 + * + * @param in 流 + * @param checksum {@link Checksum} + * @return Checksum + * @throws IORuntimeException IO异常 + * @since 4.0.10 + */ + public static Checksum checksum(InputStream in, Checksum checksum) throws IORuntimeException { + Assert.notNull(in, "InputStream is null !"); + if (null == checksum) { + checksum = new CRC32(); + } + try { + in = new CheckedInputStream(in, checksum); + IoUtil.copy(in, EmptyOutputStream.INSTANCE); + } finally { + IoUtil.close(in); + } + return checksum; + } + + /** + * 计算流的校验码,计算后关闭流 + * + * @param in 流 + * @param checksum {@link Checksum} + * @return Checksum + * @throws IORuntimeException IO异常 + * @since 5.4.0 + */ + public static long checksumValue(final InputStream in, final Checksum checksum) { + return checksum(in, checksum).getValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/copy/ChannelCopier.java b/hutool-core/src/main/java/cn/hutool/core/io/copy/ChannelCopier.java index 9ba9eaf30..1909e9943 100755 --- a/hutool-core/src/main/java/cn/hutool/core/io/copy/ChannelCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/copy/ChannelCopier.java @@ -3,7 +3,6 @@ package cn.hutool.core.io.copy; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.StreamProgress; -import cn.hutool.core.lang.Assert; import java.io.IOException; import java.io.InputStream; @@ -59,9 +58,6 @@ public class ChannelCopier extends IoCopier + * + *
{@code
+ * FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
+ * 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
+ * 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
+ *
+ * // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
+ * try (InputStream srcStream = zipFile.getInputStream(zipEntry);
+ *     ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
+ *     FileOutputStream fos = new FileOutputStream(saveFile);
+ *     FileChannel dstChannel = fos.getChannel()) {
+ *     dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
+ *  }
+ * }
+ * + * @author z8g + */ +public class FileChannelCopier extends IoCopier { + + /** + * 创建{@link FileChannel} 拷贝器 + * + * @return FileChannelCopier + */ + public static FileChannelCopier of() { + return of(-1); + } + + /** + * 创建{@link FileChannel} 拷贝器 + * + * @param count 拷贝总数,-1表示无限制 + * @return FileChannelCopier + */ + public static FileChannelCopier of(final long count) { + return new FileChannelCopier(count); + } + + /** + * 构造 + * + * @param count 拷贝总数,-1表示无限制 + */ + public FileChannelCopier(final long count) { + super(-1, count, null); + } + + /** + * 拷贝文件流,使用NIO + * + * @param in 输入 + * @param out 输出 + * @return 拷贝的字节数 + * @throws IORuntimeException IO异常 + */ + public long copy(final FileInputStream in, final FileOutputStream out) throws IORuntimeException { + FileChannel inChannel = null; + FileChannel outChannel = null; + try { + inChannel = in.getChannel(); + outChannel = out.getChannel(); + return copy(inChannel, outChannel); + } finally { + IoUtil.close(outChannel); + IoUtil.close(inChannel); + } + } + + @Override + public long copy(final FileChannel source, final FileChannel target) { + try { + return doCopySafely(source, target); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 文件拷贝实现 + * + *
+	 * FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
+	 * 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
+	 * 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
+	 *
+	 * // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
+	 * try (InputStream srcStream = zipFile.getInputStream(zipEntry);
+	 * 		ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
+	 * 		FileOutputStream fos = new FileOutputStream(saveFile);
+	 * 		FileChannel dstChannel = fos.getChannel()) {
+	 * 		dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
+	 *  }
+	 * 
+ * + * @param inChannel 输入通道 + * @param outChannel 输出通道 + * @return 输入通道的字节数 + * @throws IOException 发生IO错误 + * @link http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/java/nio/FileChannelImpl.java + * @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java + * @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/FileChannelImpl.c + * @author z8g + */ + private long doCopySafely(final FileChannel inChannel, final FileChannel outChannel) throws IOException { + long totalBytes = inChannel.size(); + if (this.count > 0 && this.count < totalBytes) { + // 限制拷贝总数 + totalBytes = count; + } + for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失 + final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数 + pos += writeBytes; + remaining -= writeBytes; + } + return totalBytes; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/copy/StreamCopier.java b/hutool-core/src/main/java/cn/hutool/core/io/copy/StreamCopier.java index 7cbdb1aec..b14f9cd1a 100755 --- a/hutool-core/src/main/java/cn/hutool/core/io/copy/StreamCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/copy/StreamCopier.java @@ -37,7 +37,7 @@ public class StreamCopier extends IoCopier { * 构造 * * @param bufferSize 缓存大小 - * @param count 拷贝总数 + * @param count 拷贝总数,-1表示无限制 */ public StreamCopier(final int bufferSize, final long count) { this(bufferSize, count, null); @@ -47,7 +47,7 @@ public class StreamCopier extends IoCopier { * 构造 * * @param bufferSize 缓存大小 - * @param count 拷贝总数 + * @param count 拷贝总数,-1表示无限制 * @param progress 进度条 */ public StreamCopier(final int bufferSize, final long count, final StreamProgress progress) { diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java index d03744fe4..0b73ad6a4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java @@ -228,7 +228,7 @@ public class FileReader extends FileWrapper { * @throws IORuntimeException IO异常 */ public BufferedReader getReader() throws IORuntimeException { - return IoUtil.getReader(getInputStream(), this.charset); + return IoUtil.toReader(getInputStream(), this.charset); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java index 75fe5030b..9a7f22a4f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java @@ -416,7 +416,7 @@ public class PathUtil { * @since 4.0.0 */ public static BufferedReader getReader(final Path path, final Charset charset) throws IORuntimeException { - return IoUtil.getReader(getInputStream(path), charset); + return IoUtil.toReader(getInputStream(path), charset); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java index 5c083ae18..ba60257d4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/CharSequenceResource.java @@ -75,7 +75,7 @@ public class CharSequenceResource implements Resource, Serializable { @Override public BufferedReader getReader(final Charset charset) { - return IoUtil.getReader(new StringReader(this.data.toString())); + return IoUtil.toBuffered(new StringReader(this.data.toString())); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java index 6dc6b8c1a..008528684 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileObjectResource.java @@ -65,7 +65,7 @@ public class FileObjectResource implements Resource { @Override public BufferedReader getReader(final Charset charset) { try { - return IoUtil.getReader(this.fileObject.openReader(false)); + return IoUtil.toBuffered(this.fileObject.openReader(false)); } catch (final IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiFileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiFileResource.java index 25920db98..449cbf5b6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiFileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiFileResource.java @@ -1,6 +1,7 @@ package cn.hutool.core.io.resource; import java.io.File; +import java.nio.file.Path; import java.util.Collection; /** @@ -8,9 +9,8 @@ import java.util.Collection; * 此资源为一个利用游标自循环资源,只有调用{@link #next()} 方法才会获取下一个资源,使用完毕后调用{@link #reset()}方法重置游标 * * @author looly - * */ -public class MultiFileResource extends MultiResource{ +public class MultiFileResource extends MultiResource { private static final long serialVersionUID = 1L; /** @@ -31,6 +31,15 @@ public class MultiFileResource extends MultiResource{ add(files); } + /** + * 构造 + * + * @param files 文件资源列表 + */ + public MultiFileResource(final Path... files) { + add(files); + } + /** * 增加文件资源 * @@ -44,6 +53,19 @@ public class MultiFileResource extends MultiResource{ return this; } + /** + * 增加文件资源 + * + * @param files 文件资源 + * @return this + */ + public MultiFileResource add(final Path... files) { + for (final Path file : files) { + this.add(new FileResource(file)); + } + return this; + } + /** * 增加文件资源 * @@ -59,6 +81,6 @@ public class MultiFileResource extends MultiResource{ @Override public MultiFileResource add(final Resource resource) { - return (MultiFileResource)super.add(resource); + return (MultiFileResource) super.add(resource); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java index cb7268151..7f4ba5e9a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java @@ -86,7 +86,7 @@ public interface Resource { * @return {@link BufferedReader} */ default BufferedReader getReader(final Charset charset) { - return IoUtil.getReader(getStream(), charset); + return IoUtil.toReader(getStream(), charset); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/BOMInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/BOMInputStream.java similarity index 97% rename from hutool-core/src/main/java/cn/hutool/core/io/BOMInputStream.java rename to hutool-core/src/main/java/cn/hutool/core/io/stream/BOMInputStream.java index a78ed72ca..2aac0e339 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/BOMInputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/BOMInputStream.java @@ -1,5 +1,6 @@ -package cn.hutool.core.io; +package cn.hutool.core.io.stream; +import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.CharsetUtil; import java.io.IOException; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/EmptyInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/EmptyInputStream.java similarity index 96% rename from hutool-core/src/main/java/cn/hutool/core/io/EmptyInputStream.java rename to hutool-core/src/main/java/cn/hutool/core/io/stream/EmptyInputStream.java index d13c53e43..6afc9ffbf 100755 --- a/hutool-core/src/main/java/cn/hutool/core/io/EmptyInputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/EmptyInputStream.java @@ -1,4 +1,4 @@ -package cn.hutool.core.io; +package cn.hutool.core.io.stream; import java.io.InputStream; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/NullOutputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/EmptyOutputStream.java similarity index 72% rename from hutool-core/src/main/java/cn/hutool/core/io/NullOutputStream.java rename to hutool-core/src/main/java/cn/hutool/core/io/stream/EmptyOutputStream.java index 6f48a8041..c6de0ac74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/NullOutputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/EmptyOutputStream.java @@ -1,4 +1,4 @@ -package cn.hutool.core.io; +package cn.hutool.core.io.stream; import java.io.IOException; import java.io.OutputStream; @@ -10,20 +10,24 @@ import java.io.OutputStream; * @author looly * @since 4.0.6 */ -public class NullOutputStream extends OutputStream { +public class EmptyOutputStream extends OutputStream { /** * 单例 */ - public static final NullOutputStream NULL_OUTPUT_STREAM = new NullOutputStream(); + public static final EmptyOutputStream INSTANCE = new EmptyOutputStream(); + + private EmptyOutputStream() { + } /** * 什么也不做,写出到{@code /dev/null}. * - * @param b 写出的数据 + * @param b 写出的数据 * @param off 开始位置 * @param len 长度 */ + @SuppressWarnings("NullableProblems") @Override public void write(final byte[] b, final int off, final int len) { // to /dev/null @@ -45,6 +49,7 @@ public class NullOutputStream extends OutputStream { * @param b 写出的数据 * @throws IOException 不抛出 */ + @SuppressWarnings("NullableProblems") @Override public void write(final byte[] b) throws IOException { // to /dev/null diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/FastByteArrayOutputStream.java similarity index 95% rename from hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java rename to hutool-core/src/main/java/cn/hutool/core/io/stream/FastByteArrayOutputStream.java index a4c4ff06b..4cabf329c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/FastByteArrayOutputStream.java @@ -1,5 +1,7 @@ -package cn.hutool.core.io; +package cn.hutool.core.io.stream; +import cn.hutool.core.io.FastByteBuffer; +import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjUtil; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/stream/StrInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/StrInputStream.java new file mode 100755 index 000000000..c98c1fe97 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/StrInputStream.java @@ -0,0 +1,47 @@ +package cn.hutool.core.io.stream; + +import cn.hutool.core.text.StrUtil; +import cn.hutool.core.util.CharsetUtil; + +import java.io.ByteArrayInputStream; +import java.nio.charset.Charset; + +/** + * 基于字符串的InputStream + * + * @author looly + * @since 6.0.0 + */ +public class StrInputStream extends ByteArrayInputStream { + + /** + * 创建StrInputStream + * + * @param str 字符串 + * @return StrInputStream + */ + public static StrInputStream ofUtf8(final CharSequence str) { + return of(str, CharsetUtil.UTF_8); + } + + /** + * 创建StrInputStream + * + * @param str 字符串 + * @param charset 编码 + * @return StrInputStream + */ + public static StrInputStream of(final CharSequence str, final Charset charset) { + return new StrInputStream(str, charset); + } + + /** + * 构造 + * + * @param str 字符串 + * @param charset 编码 + */ + public StrInputStream(final CharSequence str, final Charset charset) { + super(StrUtil.bytes(str, charset)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/stream/StreamReader.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/StreamReader.java new file mode 100755 index 000000000..b29bae21d --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/StreamReader.java @@ -0,0 +1,160 @@ +package cn.hutool.core.io.stream; + +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * {@link InputStream}读取器 + * + * @author looly + */ +public class StreamReader { + + /** + * 创建读取器 + * + * @param in {@link InputStream} + * @param closeAfterRead 读取结束后是否关闭输入流 + * @return StreamReader + */ + public static StreamReader of(final InputStream in, final boolean closeAfterRead) { + return new StreamReader(in, closeAfterRead); + } + + private final InputStream in; + private final boolean closeAfterRead; + + /** + * 构造 + * + * @param in {@link InputStream} + * @param closeAfterRead 读取结束后是否关闭输入流 + */ + public StreamReader(final InputStream in, final boolean closeAfterRead) { + this.in = in; + this.closeAfterRead = closeAfterRead; + } + + /** + * 从流中读取bytes + * + * @return bytes + * @throws IORuntimeException IO异常 + */ + public byte[] readBytes() throws IORuntimeException { + return readBytes(-1); + } + + /** + * 读取指定长度的byte数组 + * + * @param length 长度,小于0表示读取全部 + * @return bytes + * @throws IORuntimeException IO异常 + */ + public byte[] readBytes(final int length) throws IORuntimeException { + final InputStream in = this.in; + if (null == in || length == 0) { + return new byte[0]; + } + + //noinspection resource + return read(length).toByteArray(); + } + + /** + * 从流中读取内容,读到输出流中,读取完毕后可选是否关闭流 + * + * @return 输出流 + * @throws IORuntimeException IO异常 + */ + public FastByteArrayOutputStream read() throws IORuntimeException { + return read(-1); + } + + /** + * 从流中读取内容,读到输出流中,读取完毕后可选是否关闭流 + * + * @param limit 限制最大拷贝长度,-1表示无限制 + * @return 输出流 + * @throws IORuntimeException IO异常 + */ + public FastByteArrayOutputStream read(final int limit) throws IORuntimeException { + final InputStream in = this.in; + final FastByteArrayOutputStream out; + if (in instanceof FileInputStream) { + // 文件流的长度是可预见的,此时直接读取效率更高 + try { + int length = in.available(); + if (limit > 0 && limit < length) { + length = limit; + } + out = new FastByteArrayOutputStream(length); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } else { + out = new FastByteArrayOutputStream(); + } + try { + IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, limit, null); + } finally { + if (closeAfterRead) { + IoUtil.close(in); + } + } + return out; + } + + /** + * 从流中读取对象,即对象的反序列化 + * + *

注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!!

+ * + *

+ * 此方法使用了{@link ValidateObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞
+ * 通过构造{@link ValidateObjectInputStream},调用{@link ValidateObjectInputStream#accept(Class[])} + * 或者{@link ValidateObjectInputStream#refuse(Class[])}方法添加可以被序列化的类或者禁止序列化的类。 + *

+ * + * @param 读取对象的类型 + * @param acceptClasses 读取对象类型 + * @return 输出流 + * @throws IORuntimeException IO异常 + * @throws UtilException ClassNotFoundException包装 + */ + @SuppressWarnings("unchecked") + public T readObj(final Class... acceptClasses) throws IORuntimeException, UtilException { + final InputStream in = this.in; + if (null == in) { + return null; + } + + // 转换 + final ValidateObjectInputStream validateIn; + if (in instanceof ValidateObjectInputStream) { + validateIn = (ValidateObjectInputStream) in; + validateIn.accept(acceptClasses); + } else { + try { + validateIn = new ValidateObjectInputStream(in, acceptClasses); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + // 读取 + try { + return (T) validateIn.readObject(); + } catch (final IOException e) { + throw new IORuntimeException(e); + } catch (final ClassNotFoundException e) { + throw new UtilException(e); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/stream/StreamWriter.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/StreamWriter.java new file mode 100755 index 000000000..9cca8f043 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/StreamWriter.java @@ -0,0 +1,115 @@ +package cn.hutool.core.io.stream; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrUtil; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + +/** + * {@link OutputStream}写出器 + * + * @author looly + */ +public class StreamWriter { + + /** + * 创建写出器 + * + * @param out {@link OutputStream} + * @param closeAfterWrite 写出结束后是否关闭流 + * @return StreamReader + */ + public static StreamWriter of(final OutputStream out, final boolean closeAfterWrite) { + return new StreamWriter(out, closeAfterWrite); + } + + private final OutputStream out; + private final boolean closeAfterWrite; + + /** + * 构造 + * + * @param out {@link OutputStream} + * @param closeAfterWrite 写出结束后是否关闭流 + */ + public StreamWriter(final OutputStream out, final boolean closeAfterWrite) { + this.out = out; + this.closeAfterWrite = closeAfterWrite; + } + + /** + * 将byte[]写到流中 + * + * @param content 写入的内容 + * @throws IORuntimeException IO异常 + */ + public void write(final byte[] content) throws IORuntimeException { + final OutputStream out = this.out; + try { + out.write(content); + } catch (final IOException e) { + throw new IORuntimeException(e); + } finally { + if (closeAfterWrite) { + IoUtil.close(out); + } + } + } + + /** + * 将多部分对象写到流中,使用{@link ObjectOutputStream},对象必须实现序列化接口 + * + * @param contents 写入的内容 + * @throws IORuntimeException IO异常 + */ + public void writeObj(final Object... contents) throws IORuntimeException { + ObjectOutputStream osw = null; + try { + osw = out instanceof ObjectOutputStream ? (ObjectOutputStream) out : new ObjectOutputStream(out); + for (final Object content : contents) { + if (content != null) { + osw.writeObject(content); + } + } + osw.flush(); + } catch (final IOException e) { + throw new IORuntimeException(e); + } finally { + if (closeAfterWrite) { + IoUtil.close(osw); + } + } + } + + /** + * 将多部分内容写到流中,自动转换为字符串 + * + * @param charset 写出的内容的字符集 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @throws IORuntimeException IO异常 + */ + public void writeStr(final Charset charset, final Object... contents) throws IORuntimeException { + OutputStreamWriter osw = null; + try { + osw = IoUtil.toWriter(out, charset); + for (final Object content : contents) { + if (content != null) { + osw.write(Convert.toStr(content, StrUtil.EMPTY)); + } + } + osw.flush(); + } catch (final IOException e) { + throw new IORuntimeException(e); + } finally { + if (closeAfterWrite) { + IoUtil.close(osw); + } + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/ValidateObjectInputStream.java similarity index 98% rename from hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java rename to hutool-core/src/main/java/cn/hutool/core/io/stream/ValidateObjectInputStream.java index 788305cbf..850f381fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/ValidateObjectInputStream.java @@ -1,4 +1,4 @@ -package cn.hutool.core.io; +package cn.hutool.core.io.stream; import cn.hutool.core.collection.CollUtil; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/stream/package-info.java b/hutool-core/src/main/java/cn/hutool/core/io/stream/package-info.java new file mode 100755 index 000000000..ebf2a5002 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/stream/package-info.java @@ -0,0 +1,6 @@ +/** + * InputStream和OutputStream相关方法和类封装 + * + * @author looly + */ +package cn.hutool.core.io.stream; diff --git a/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java index 333f813e5..0425cc3ba 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java @@ -1,6 +1,6 @@ package cn.hutool.core.net.multipart; -import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/url/URLUtil.java index 163dfcf78..69d1213a4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/URLUtil.java @@ -465,7 +465,7 @@ public class URLUtil { * @since 3.2.1 */ public static BufferedReader getReader(final URL url, final Charset charset) { - return IoUtil.getReader(getStream(url), charset); + return IoUtil.toReader(getStream(url), charset); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index c4964cd87..6381187bb 100755 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -2506,7 +2506,7 @@ public class CharSequenceUtil extends StrChecker { * @param charset 编码 * @return byteBuffer */ - public static ByteBuffer byteBuffer(final CharSequence str, final String charset) { + public static ByteBuffer byteBuffer(final CharSequence str, final Charset charset) { return ByteBuffer.wrap(bytes(str, charset)); } diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java index f834031c8..b57267d41 100755 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java @@ -1,6 +1,6 @@ package cn.hutool.core.io; -import cn.hutool.core.codec.HexUtil; +import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Ignore; @@ -12,21 +12,32 @@ import java.io.IOException; /** * 文件类型判断单元测试 - * @author Looly * + * @author Looly */ public class FileTypeUtilTest { @Test - @Ignore - public void fileTypeUtilTest() { + public void getTypeTest() { + final String type = FileTypeUtil.getType(ResourceUtil.getStream("hutool.jpg")); + Assert.assertEquals("jpg", type); + } + + @Test + public void customTypeTest() { final File file = FileUtil.file("hutool.jpg"); final String type = FileTypeUtil.getType(file); Assert.assertEquals("jpg", type); - FileTypeUtil.putFileType("ffd8ffe000104a464946", "new_jpg"); + final String oldType = FileTypeUtil.putFileType("ffd8ffe000104a464946", "new_jpg"); + Assert.assertNull(oldType); + final String newType = FileTypeUtil.getType(file); Assert.assertEquals("new_jpg", newType); + + FileTypeUtil.removeFileType("ffd8ffe000104a464946"); + final String type2 = FileTypeUtil.getType(file); + Assert.assertEquals("jpg", type2); } @Test @@ -49,7 +60,7 @@ public class FileTypeUtilTest { @Ignore public void ofdTest() { final File file = FileUtil.file("e:/test.ofd"); - final String hex = IoUtil.readHex28Upper(FileUtil.getInputStream(file)); + final String hex = FileTypeUtil.readHex28Upper(FileUtil.getInputStream(file)); Console.log(hex); final String type = FileTypeUtil.getType(file); Console.log(type); @@ -72,13 +83,14 @@ public class FileTypeUtilTest { final BufferedInputStream inputStream = FileUtil.getInputStream(file); inputStream.mark(0); final String type = FileTypeUtil.getType(inputStream); + Assert.assertEquals("jpg", type); inputStream.reset(); } @Test @Ignore - public void webpTest(){ + public void webpTest() { // https://gitee.com/dromara/hutool/issues/I5BGTF final File file = FileUtil.file("d:/test/a.webp"); final BufferedInputStream inputStream = FileUtil.getInputStream(file); @@ -87,8 +99,14 @@ public class FileTypeUtilTest { } @Test - public void getHexTest() { - final String id3 = HexUtil.encodeHexStr("8BPS"); - Console.log(id3); + public void readHex28LowerTest() { + final String s = FileTypeUtil.readHex28Lower(ResourceUtil.getStream("hutool.jpg")); + Assert.assertEquals("ffd8ffe000104a46494600010101006000600000ffe1095845786966", s); + } + + @Test + public void readHex28UpperTest() { + final String s = FileTypeUtil.readHex28Upper(ResourceUtil.getStream("hutool.jpg")); + Assert.assertEquals("FFD8FFE000104A46494600010101006000600000FFE1095845786966", s); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/io/IoUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/IoUtilTest.java index 9462c3234..dee7b2806 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/IoUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/IoUtilTest.java @@ -1,13 +1,27 @@ package cn.hutool.core.io; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.collection.iter.LineIter; import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.io.stream.EmptyOutputStream; +import cn.hutool.core.io.stream.StrInputStream; import cn.hutool.core.lang.func.SerConsumer; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.RandomUtil; import org.junit.Assert; import org.junit.Test; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackReader; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; public class IoUtilTest { @@ -33,4 +47,111 @@ public class IoUtilTest { throw new IORuntimeException(e); } } + + @Test + public void readUtf8LinesTest() { + final ArrayList strings = IoUtil.readUtf8Lines(ResourceUtil.getStream("text.txt"), ListUtil.of()); + Assert.assertEquals(3, strings.size()); + } + + @Test + public void readUtf8LinesTest2() { + IoUtil.readUtf8Lines(ResourceUtil.getStream("text.txt"), (SerConsumer) Assert::assertNotNull); + } + + @Test + public void toBufferedTest() { + final BufferedInputStream stream = IoUtil.toBuffered( + new ByteArrayInputStream("hutool".getBytes()), IoUtil.DEFAULT_BUFFER_SIZE); + + Assert.assertNotNull(stream); + Assert.assertEquals("hutool", IoUtil.readUtf8(stream)); + } + + @Test + public void toBufferedOfOutTest() { + final BufferedOutputStream stream = IoUtil.toBuffered( + EmptyOutputStream.INSTANCE, 512); + + Assert.assertNotNull(stream); + } + + @Test + public void toBufferedOfReaderTest() { + final BufferedReader reader = IoUtil.toBuffered( + new StringReader("hutool"), 512); + + Assert.assertNotNull(reader); + + Assert.assertEquals("hutool", IoUtil.read(reader)); + } + + @Test + public void toBufferedOfWriterTest() throws IOException { + final StringWriter stringWriter = new StringWriter(); + final BufferedWriter writer = IoUtil.toBuffered( + stringWriter, 512); + + Assert.assertNotNull(writer); + writer.write("hutool"); + writer.flush(); + + Assert.assertEquals("hutool", stringWriter.toString()); + } + + @Test + public void toBufferedOfWriterTest2() throws IOException { + final StringWriter stringWriter = new StringWriter(); + final BufferedWriter writer = IoUtil.toBuffered(stringWriter); + + Assert.assertNotNull(writer); + writer.write("hutool"); + writer.flush(); + + Assert.assertEquals("hutool", stringWriter.toString()); + } + + @Test + public void toPushBackReaderTest() throws IOException { + final PushbackReader reader = IoUtil.toPushBackReader(new StringReader("hutool"), 12); + final int read = reader.read(); + Assert.assertEquals('h', read); + reader.unread(read); + + Assert.assertEquals("hutool", IoUtil.read(reader)); + } + + @Test + public void toAvailableStreamTest() { + final InputStream in = IoUtil.toAvailableStream(StrInputStream.ofUtf8("hutool")); + final String read = IoUtil.readUtf8(in); + Assert.assertEquals("hutool", read); + } + + @Test + public void writeCloseTest() { + IoUtil.writeClose(EmptyOutputStream.INSTANCE, "hutool".getBytes()); + } + + @Test + public void writeUtf8Test() { + IoUtil.writeUtf8(EmptyOutputStream.INSTANCE, false, "hutool"); + } + + @Test + public void closeIfPossibleTest() { + IoUtil.closeIfPossible(new Object()); + } + + @Test + public void contentEqualsTest() { + final boolean b = IoUtil.contentEquals(new StringReader("hutool"), new StringReader("hutool")); + Assert.assertTrue(b); + } + + @Test + public void lineIterTest() { + final LineIter strings = IoUtil.lineIter(ResourceUtil.getStream("text.txt"), CharsetUtil.UTF_8); + strings.forEach(Assert::assertNotNull); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/io/NioUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/NioUtilTest.java new file mode 100755 index 000000000..b1d9f4d3e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/io/NioUtilTest.java @@ -0,0 +1,24 @@ +package cn.hutool.core.io; + +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.io.stream.EmptyOutputStream; +import cn.hutool.core.text.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.io.IOException; +import java.nio.channels.FileChannel; + +public class NioUtilTest { + @Test + public void copyByNIOTest() { + final long size = NioUtil.copyByNIO(ResourceUtil.getStream("hutool.jpg"), EmptyOutputStream.INSTANCE, NioUtil.DEFAULT_MIDDLE_BUFFER_SIZE, null); + Assert.assertEquals(22807, size); + } + + @Test + public void readUtf8Test() throws IOException { + final String s = NioUtil.readUtf8(FileChannel.open(FileUtil.file("text.txt").toPath())); + Assert.assertTrue(StrUtil.isNotBlank(s)); + } +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/ASN1Util.java b/hutool-crypto/src/main/java/cn/hutool/crypto/ASN1Util.java index d4ff26d90..68b73911b 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/ASN1Util.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/ASN1Util.java @@ -1,6 +1,6 @@ package cn.hutool.crypto; -import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; import cn.hutool.core.io.IORuntimeException; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Encoding; diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java index c94d7fb60..6017e9966 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/PemUtil.java @@ -122,7 +122,7 @@ public class PemUtil { * @since 4.5.2 */ public static PemObject readPemObject(final InputStream keyStream) { - return readPemObject(IoUtil.getUtf8Reader(keyStream)); + return readPemObject(IoUtil.toUtf8Reader(keyStream)); } /** @@ -189,7 +189,7 @@ public class PemUtil { * @since 5.1.6 */ public static void writePemObject(final PemObjectGenerator pemObject, final OutputStream keyStream) { - writePemObject(pemObject, IoUtil.getUtf8Writer(keyStream)); + writePemObject(pemObject, IoUtil.toUtf8Writer(keyStream)); } /** diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java index 0f18ec68e..4019b3050 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricCrypto.java @@ -1,7 +1,7 @@ package cn.hutool.crypto.asymmetric; import cn.hutool.core.codec.BaseN.Base64; -import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; import cn.hutool.crypto.CipherWrapper; import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.KeyUtil; diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricEncryptor.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricEncryptor.java index 0a420cee7..02d1922f8 100755 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricEncryptor.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricEncryptor.java @@ -54,18 +54,6 @@ public interface AsymmetricEncryptor { return Base64.encode(encrypt(data, keyType)); } - /** - * 加密 - * - * @param data 被加密的字符串 - * @param charset 编码 - * @param keyType 私钥或公钥 {@link KeyType} - * @return 加密后的bytes - */ - default byte[] encrypt(final String data, final String charset, final KeyType keyType) { - return encrypt(StrUtil.bytes(data, charset), keyType); - } - /** * 加密 * diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricEncryptor.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricEncryptor.java index 3f67517d5..3b3e91e42 100755 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricEncryptor.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricEncryptor.java @@ -62,17 +62,6 @@ public interface SymmetricEncryptor { return Base64.encode(encrypt(data)); } - /** - * 加密 - * - * @param data 被加密的字符串 - * @param charset 编码 - * @return 加密后的bytes - */ - default byte[] encrypt(final String data, final String charset) { - return encrypt(StrUtil.bytes(data, charset)); - } - /** * 加密 * @@ -84,17 +73,6 @@ public interface SymmetricEncryptor { return encrypt(StrUtil.bytes(data, charset)); } - /** - * 加密 - * - * @param data 被加密的字符串 - * @param charset 编码 - * @return 加密后的Hex - */ - default String encryptHex(final String data, final String charset) { - return HexUtil.encodeHexStr(encrypt(data, charset)); - } - /** * 加密 * @@ -106,17 +84,6 @@ public interface SymmetricEncryptor { return HexUtil.encodeHexStr(encrypt(data, charset)); } - /** - * 加密 - * - * @param data 被加密的字符串 - * @param charset 编码 - * @return 加密后的Base64 - */ - default String encryptBase64(final String data, final String charset) { - return Base64.encode(encrypt(data, charset)); - } - /** * 加密 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/FreemarkerTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/FreemarkerTemplate.java index 0540ff76b..397f6b0ad 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/FreemarkerTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/FreemarkerTemplate.java @@ -54,7 +54,7 @@ public class FreemarkerTemplate implements Template, Serializable{ @Override public void render(final Map bindingMap, final OutputStream out) { - render(bindingMap, IoUtil.getWriter(out, Charset.forName(this.rawTemplate.getEncoding()))); + render(bindingMap, IoUtil.toWriter(out, Charset.forName(this.rawTemplate.getEncoding()))); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java index 3fe35f0e1..a5c382a77 100755 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java @@ -63,7 +63,7 @@ public class ThymeleafTemplate implements Template, Serializable { @Override public void render(final Map bindingMap, final OutputStream out) { - render(bindingMap, IoUtil.getWriter(out, this.charset)); + render(bindingMap, IoUtil.toWriter(out, this.charset)); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java index 08db8f269..8191f0665 100755 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java @@ -56,7 +56,7 @@ public class VelocityTemplate implements Template, Serializable { if(null == charset) { loadEncoding(); } - render(bindingMap, IoUtil.getWriter(out, CharsetUtil.charset(this.charset))); + render(bindingMap, IoUtil.toWriter(out, CharsetUtil.charset(this.charset))); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/client/HttpDownloader.java b/hutool-http/src/main/java/cn/hutool/http/client/HttpDownloader.java index 8bcf252d0..435f8997d 100644 --- a/hutool-http/src/main/java/cn/hutool/http/client/HttpDownloader.java +++ b/hutool-http/src/main/java/cn/hutool/http/client/HttpDownloader.java @@ -1,6 +1,6 @@ package cn.hutool.http.client; -import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; import cn.hutool.core.io.StreamProgress; import cn.hutool.core.lang.Assert; import cn.hutool.http.HttpException; diff --git a/hutool-http/src/main/java/cn/hutool/http/client/body/FormBody.java b/hutool-http/src/main/java/cn/hutool/http/client/body/FormBody.java new file mode 100755 index 000000000..3fd6a6089 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/client/body/FormBody.java @@ -0,0 +1,120 @@ +package cn.hutool.http.client.body; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.resource.FileResource; +import cn.hutool.core.io.resource.MultiFileResource; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.TableMap; +import cn.hutool.core.text.StrUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjUtil; + +import java.io.File; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.Map; + +/** + * Form表单形式的消息体 + * + * @param this类型,用于链式调用 + * @author looly + */ +@SuppressWarnings("unchecked") +public abstract class FormBody> implements RequestBody { + /** + * 存储表单数据 + */ + protected Map form; + /** + * 编码 + */ + protected final Charset charset; + + /** + * 构造 + * + * @param form 表单 + * @param charset 编码 + */ + protected FormBody(final Map form, final Charset charset) { + this.form = form; + this.charset = charset; + } + + /** + * 设置map类型表单数据 + * + * @param formMap 表单内容 + * @return this + */ + public T form(final Map formMap) { + if (MapUtil.isNotEmpty(formMap)) { + formMap.forEach(this::form); + } + return (T) this; + } + + /** + * 设置表单数据
+ * 如果传入值为{@code null},则移除这个表单项 + * + * @param name 名,blank则忽略之 + * @param value 值为{@code null},则移除这个表单项 + * @return this + */ + public T form(final String name, final Object value) { + if (StrUtil.isBlank(name)) { + return (T) this; // 忽略非法的form表单项内容; + } + if(ObjUtil.isNull(value)){ + this.form.remove(name); + } + + if (value instanceof File) { + return putToForm(name, new FileResource((File) value)); + } else if(value instanceof Path){ + return putToForm(name, new FileResource((Path) value)); + } + + // 普通值 + final String strValue; + if (value instanceof Iterable) { + // 列表对象 + strValue = CollUtil.join((Iterable) value, ","); + } else if (ArrayUtil.isArray(value)) { + final Class componentType = ArrayUtil.getComponentType(value); + // 多文件 + if (File.class == componentType) { + return putToForm(name, new MultiFileResource((File[])value)); + } else if (Path.class == componentType) { + return putToForm(name, new MultiFileResource((Path[])value)); + } + // 数组对象 + strValue = ArrayUtil.join(value, ","); + } else { + // 其他对象一律转换为字符串 + strValue = Convert.toStr(value, null); + } + + return putToForm(name, strValue); + } + + /** + * 将参数加入到form中,如果form为空,新建之。 + * + * @param name 表单属性名 + * @param value 属性值 + * @return this + */ + private T putToForm(final String name, final Object value) { + if (null != name && null != value) { + if (null == this.form) { + this.form = new TableMap<>(16); + } + this.form.put(name, value); + } + return (T) this; + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/client/body/MultipartBody.java b/hutool-http/src/main/java/cn/hutool/http/client/body/MultipartBody.java index 8382562f6..f3fa4e8fb 100644 --- a/hutool-http/src/main/java/cn/hutool/http/client/body/MultipartBody.java +++ b/hutool-http/src/main/java/cn/hutool/http/client/body/MultipartBody.java @@ -17,32 +17,48 @@ import java.util.Map; * @author looly * @since 5.3.5 */ -public class MultipartBody implements RequestBody { +public class MultipartBody extends FormBody { private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; - /** - * 存储表单数据 - */ - private final Map form; - /** - * 编码 - */ - private final Charset charset; /** * 边界 */ - private final String boundary = HttpGlobalConfig.getBoundary(); + private final String boundary; /** - * 根据已有表单内容,构建MultipartBody + * 根据已有表单内容,构建MultipartBody,使用全局默认的边界符{@link HttpGlobalConfig#getBoundary()} * * @param form 表单 * @param charset 编码 * @return MultipartBody */ public static MultipartBody of(final Map form, final Charset charset) { - return new MultipartBody(form, charset); + return of(form, charset, HttpGlobalConfig.getBoundary()); + } + + /** + * 根据已有表单内容,构建MultipartBody + * + * @param form 表单 + * @param charset 编码 + * @param boundary Multipart边界符 + * @return MultipartBody + */ + public static MultipartBody of(final Map form, final Charset charset, final String boundary) { + return new MultipartBody(form, charset, boundary); + } + + /** + * 构造 + * + * @param form 表单 + * @param charset 编码 + * @param boundary Multipart边界符 + */ + public MultipartBody(final Map form, final Charset charset, final String boundary) { + super(form, charset); + this.boundary = boundary; } /** @@ -54,17 +70,6 @@ public class MultipartBody implements RequestBody { return CONTENT_TYPE_MULTIPART_PREFIX + boundary; } - /** - * 构造 - * - * @param form 表单 - * @param charset 编码 - */ - public MultipartBody(final Map form, final Charset charset) { - this.form = form; - this.charset = charset; - } - /** * 写出Multiparty数据,不关闭流 * diff --git a/hutool-http/src/main/java/cn/hutool/http/client/body/RequestBody.java b/hutool-http/src/main/java/cn/hutool/http/client/body/RequestBody.java index 94646e1db..8aef7b60a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/client/body/RequestBody.java +++ b/hutool-http/src/main/java/cn/hutool/http/client/body/RequestBody.java @@ -1,6 +1,6 @@ package cn.hutool.http.client.body; -import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; import cn.hutool.core.io.IoUtil; import java.io.InputStream; diff --git a/hutool-http/src/main/java/cn/hutool/http/client/body/FormUrlEncodedBody.java b/hutool-http/src/main/java/cn/hutool/http/client/body/UrlEncodedFormBody.java similarity index 51% rename from hutool-http/src/main/java/cn/hutool/http/client/body/FormUrlEncodedBody.java rename to hutool-http/src/main/java/cn/hutool/http/client/body/UrlEncodedFormBody.java index 4160852c5..109769373 100644 --- a/hutool-http/src/main/java/cn/hutool/http/client/body/FormUrlEncodedBody.java +++ b/hutool-http/src/main/java/cn/hutool/http/client/body/UrlEncodedFormBody.java @@ -1,8 +1,10 @@ package cn.hutool.http.client.body; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.text.StrUtil; +import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Map; @@ -12,7 +14,7 @@ import java.util.Map; * @author looly * @since 5.7.17 */ -public class FormUrlEncodedBody extends BytesBody { +public class UrlEncodedFormBody extends FormBody { /** * 创建 Http request body @@ -21,8 +23,8 @@ public class FormUrlEncodedBody extends BytesBody { * @param charset 编码 * @return FormUrlEncodedBody */ - public static FormUrlEncodedBody of(final Map form, final Charset charset) { - return new FormUrlEncodedBody(form, charset); + public static UrlEncodedFormBody of(final Map form, final Charset charset) { + return new UrlEncodedFormBody(form, charset); } /** @@ -31,8 +33,13 @@ public class FormUrlEncodedBody extends BytesBody { * @param form 表单 * @param charset 编码 */ - public FormUrlEncodedBody(final Map form, final Charset charset) { - super(StrUtil.bytes(UrlQuery.of(form, true).build(charset), charset)); + public UrlEncodedFormBody(final Map form, final Charset charset) { + super(form, charset); } + @Override + public void write(final OutputStream out) { + final byte[] bytes = StrUtil.bytes(UrlQuery.of(form, true).build(charset), charset); + IoUtil.write(out, false, bytes); + } } diff --git a/hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/HttpRequest.java index 7602c5ed1..092ccfbf6 100755 --- a/hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/HttpRequest.java @@ -26,7 +26,7 @@ import cn.hutool.http.meta.HttpStatus; import cn.hutool.http.HttpUtil; import cn.hutool.http.meta.Method; import cn.hutool.http.client.body.BytesBody; -import cn.hutool.http.client.body.FormUrlEncodedBody; +import cn.hutool.http.client.body.UrlEncodedFormBody; import cn.hutool.http.client.body.MultipartBody; import cn.hutool.http.client.body.RequestBody; import cn.hutool.http.client.cookie.GlobalCookieManager; @@ -1311,7 +1311,7 @@ public class HttpRequest extends HttpBase { if (ArrayUtil.isNotEmpty(this.bodyBytes)) { body = BytesBody.of(this.bodyBytes); } else { - body = FormUrlEncodedBody.of(this.form, this.charset); + body = UrlEncodedFormBody.of(this.form, this.charset); } body.writeClose(this.httpConnection.getOutputStream()); } diff --git a/hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/HttpResponse.java index 5bec6c58d..9e362c843 100755 --- a/hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/HttpResponse.java @@ -1,7 +1,7 @@ package cn.hutool.http.client.engine.jdk; import cn.hutool.core.convert.Convert; -import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.stream.FastByteArrayOutputStream; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; diff --git a/hutool-http/src/main/java/cn/hutool/http/client/engine/okhttp/OkHttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/client/engine/okhttp/OkHttpResponse.java index 657395901..228dcd7ab 100755 --- a/hutool-http/src/main/java/cn/hutool/http/client/engine/okhttp/OkHttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/client/engine/okhttp/OkHttpResponse.java @@ -1,6 +1,6 @@ package cn.hutool.http.client.engine.okhttp; -import cn.hutool.core.io.EmptyInputStream; +import cn.hutool.core.io.stream.EmptyInputStream; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.http.HttpUtil; diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index 0a9d43fe0..a3ecf3c9f 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -74,7 +74,7 @@ public class JSONTokener { * @throws JSONException JSON异常,包装IO异常 */ public JSONTokener(final InputStream inputStream, final JSONConfig config) throws JSONException { - this(IoUtil.getUtf8Reader(inputStream), config); + this(IoUtil.toUtf8Reader(inputStream), config); } /** diff --git a/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java b/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java index ae9f68990..40d595e2a 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java @@ -194,7 +194,7 @@ public class GroupedSet extends HashMap> { super.clear(); BufferedReader reader = null; try { - reader = IoUtil.getReader(settingStream, charset); + reader = IoUtil.toReader(settingStream, charset); // 分组 String group; LinkedHashSet valueSet = null; diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java index 38530cafd..4735fe91f 100755 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java @@ -103,7 +103,7 @@ public class SettingLoader { this.groupedMap.clear(); BufferedReader reader = null; try { - reader = IoUtil.getReader(settingStream, this.charset); + reader = IoUtil.toReader(settingStream, this.charset); // 分组 String group = null; diff --git a/hutool-setting/src/main/java/cn/hutool/setting/yaml/YamlUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/yaml/YamlUtil.java index d8a74a12f..df49e38c8 100755 --- a/hutool-setting/src/main/java/cn/hutool/setting/yaml/YamlUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/yaml/YamlUtil.java @@ -50,7 +50,7 @@ public class YamlUtil { * @return 加载的内容,默认Map */ public static T load(final InputStream in, final Class type) { - return load(IoUtil.getBomReader(in), type); + return load(IoUtil.toBomReader(in), type); } /**