From 4b3941b2f81571a73cb57a4a07aede1ae38720c8 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 13 Nov 2020 02:45:10 +0800 Subject: [PATCH] add compress --- CHANGELOG.md | 1 + hutool-extra/pom.xml | 13 ++ .../extra/compress/CompressException.java | 33 ++++ .../hutool/extra/compress/CompressUtil.java | 66 +++++++ .../cn/hutool/extra/compress/Extractor.java | 4 + .../extra/compress/archiver/Archiver.java | 59 ++++++ .../compress/archiver/SevenZArchiver.java | 136 ++++++++++++++ .../compress/archiver/StreamArchiver.java | 169 ++++++++++++++++++ .../hutool/extra/compress/package-info.java | 13 ++ .../cn/hutool/extra/ftp/SimpleFtpServer.java | 18 +- .../hutool/extra/compress/ArchiverTest.java | 50 ++++++ 11 files changed, 553 insertions(+), 9 deletions(-) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/CompressException.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/StreamArchiver.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/package-info.java create mode 100644 hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index d38d14e5b..da5e4695e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee) * 【core 】 CollUtil和IterUtil增加size方法(pr#208@Gitee) * 【extra 】 新增SimpleFtpServer +* 【extra 】 新增CompressUtil压缩封装 ### Bug修复 * 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f448e775..88db4ba94 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -411,5 +411,18 @@ true + + org.apache.commons + commons-compress + 1.20 + compile + true + + + org.tukaani + xz + 1.8 + test + diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressException.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressException.java new file mode 100644 index 000000000..c182b1151 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressException.java @@ -0,0 +1,33 @@ +package cn.hutool.extra.compress; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 压缩解压异常语言异常 + * + * @author Looly + */ +public class CompressException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public CompressException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public CompressException(String message) { + super(message); + } + + public CompressException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public CompressException(String message, Throwable throwable) { + super(message, throwable); + } + + public CompressException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java new file mode 100644 index 000000000..5924ec054 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java @@ -0,0 +1,66 @@ +package cn.hutool.extra.compress; + +import cn.hutool.extra.compress.archiver.Archiver; +import cn.hutool.extra.compress.archiver.SevenZArchiver; +import cn.hutool.extra.compress.archiver.StreamArchiver; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; + +import java.io.File; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * 压缩工具类
+ * 基于commons-compress的压缩解压封装 + * + * @since 5.5.0 + * @author looly + */ +public class CompressUtil { + + /** + * 创建归档器,支持: + * + * + * @param charset 编码 + * @param archiverName 归档类型名称,见{@link ArchiveStreamFactory} + * @param file 归档输出的文件 + * @return Archiver + */ + public static Archiver createArchiver(Charset charset, String archiverName, File file) { + if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) { + return new SevenZArchiver(file); + } + return StreamArchiver.create(charset, archiverName, file); + } + + /** + * 创建归档器,支持: + * + * + * @param charset 编码 + * @param archiverName 归档类型名称,见{@link ArchiveStreamFactory} + * @param out 归档输出的流 + * @return Archiver + */ + public static Archiver createArchiver(Charset charset, String archiverName, OutputStream out) { + if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) { + return new SevenZArchiver(out); + } + return StreamArchiver.create(charset, archiverName, out); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java new file mode 100644 index 000000000..7819de07f --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java @@ -0,0 +1,4 @@ +package cn.hutool.extra.compress; + +public class Extractor { +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java new file mode 100644 index 000000000..c13f3a4e7 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java @@ -0,0 +1,59 @@ +package cn.hutool.extra.compress.archiver; + +import cn.hutool.core.util.StrUtil; + +import java.io.Closeable; +import java.io.File; +import java.io.FileFilter; + +/** + * 数据归档封装,归档即将几个文件或目录打成一个压缩包
+ * + * @author looly + */ +public interface Archiver extends Closeable { + + /** + * 将文件或目录加入归档,目录采取递归读取方式按照层级加入 + * + * @param file 文件或目录 + * @return this + */ + default Archiver add(File file) { + return add(file, null); + } + + /** + * 将文件或目录加入归档,目录采取递归读取方式按照层级加入 + * + * @param file 文件或目录 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @return this + */ + default Archiver add(File file, FileFilter filter) { + return add(file, StrUtil.SLASH, filter); + } + + /** + * 将文件或目录加入归档包,目录采取递归读取方式按照层级加入 + * + * @param file 文件或目录 + * @param path 文件或目录的初始路径,null表示位于根路径 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @return this + */ + Archiver add(File file, String path, FileFilter filter); + + /** + * 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件 + * + * @return this + */ + Archiver finish(); + + /** + * 无异常关闭 + */ + @Override + void close(); +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java new file mode 100644 index 000000000..9612defd7 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java @@ -0,0 +1,136 @@ +package cn.hutool.extra.compress.archiver; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.SeekableByteChannel; + +/** + * 7zip格式的归档封装 + * + * @author looly + */ +public class SevenZArchiver implements Archiver { + + private final SevenZOutputFile sevenZOutputFile; + + private SeekableByteChannel channel; + private OutputStream out; + + /** + * 构造 + * + * @param file 归档输出的文件 + */ + public SevenZArchiver(File file) { + try { + this.sevenZOutputFile = new SevenZOutputFile(file); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 构造 + * + * @param out 归档输出的流 + */ + public SevenZArchiver(OutputStream out) { + this.out = out; + this.channel = new SeekableInMemoryByteChannel(); + try { + this.sevenZOutputFile = new SevenZOutputFile(channel); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 构造 + * + * @param channel 归档输出的文件 + */ + public SevenZArchiver(SeekableByteChannel channel) { + try { + this.sevenZOutputFile = new SevenZOutputFile(channel); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + @Override + public SevenZArchiver add(File file, String path, FileFilter filter) { + try { + addInternal(file, path, filter); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + @Override + public SevenZArchiver finish() { + try { + this.sevenZOutputFile.finish(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + @Override + public void close() { + try { + finish(); + } catch (Exception ignore) { + //ignore + } + if(null != out && this.channel instanceof SeekableInMemoryByteChannel){ + try { + out.write(((SeekableInMemoryByteChannel)this.channel).array()); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + IoUtil.close(this.sevenZOutputFile); + } + + /** + * 将文件或目录加入归档包,目录采取递归读取方式按照层级加入 + * + * @param file 文件或目录 + * @param path 文件或目录的初始路径,null表示位于根路径 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + */ + private void addInternal(File file, String path, FileFilter filter) throws IOException { + if (null != filter && false == filter.accept(file)) { + return; + } + final SevenZOutputFile out = this.sevenZOutputFile; + + final String entryName = StrUtil.addSuffixIfNot(StrUtil.nullToEmpty(path), StrUtil.SLASH) + file.getName(); + out.putArchiveEntry(out.createArchiveEntry(file, entryName)); + + if (file.isDirectory()) { + // 目录遍历写入 + final File[] files = file.listFiles(); + for (File childFile : files) { + addInternal(childFile, entryName, filter); + } + } else { + if (file.isFile()) { + // 文件直接写入 + out.write(FileUtil.readBytes(file)); + } + out.closeArchiveEntry(); + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/StreamArchiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/StreamArchiver.java new file mode 100644 index 000000000..fb2feac82 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/StreamArchiver.java @@ -0,0 +1,169 @@ +package cn.hutool.extra.compress.archiver; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.compress.CompressException; +import org.apache.commons.compress.archivers.ArchiveException; +import org.apache.commons.compress.archivers.ArchiveOutputStream; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * 数据归档封装,归档即将几个文件或目录打成一个压缩包
+ * 支持的归档文件格式为: + * + * + * @author looly + */ +public class StreamArchiver implements Archiver { + + /** + * 创建归档器 + * + * @param charset 编码 + * @param archiverName 归档类型名称,见{@link ArchiveStreamFactory} + * @param file 归档输出的文件 + * @return StreamArchiver + */ + public static StreamArchiver create(Charset charset, String archiverName, File file) { + return new StreamArchiver(charset, archiverName, file); + } + + /** + * 创建归档器 + * + * @param charset 编码 + * @param archiverName 归档类型名称,见{@link ArchiveStreamFactory} + * @param out 归档输出的流 + * @return StreamArchiver + */ + public static StreamArchiver create(Charset charset, String archiverName, OutputStream out) { + return new StreamArchiver(charset, archiverName, out); + } + + private ArchiveOutputStream out; + + /** + * 构造 + * + * @param charset 编码 + * @param archiverName 归档类型名称,见{@link ArchiveStreamFactory} + * @param file 归档输出的文件 + */ + public StreamArchiver(Charset charset, String archiverName, File file) { + this(charset, archiverName, FileUtil.getOutputStream(file)); + } + + /** + * 构造 + * + * @param charset 编码 + * @param archiverName 归档类型名称,见{@link ArchiveStreamFactory} + * @param targetStream 归档输出的流 + */ + public StreamArchiver(Charset charset, String archiverName, OutputStream targetStream) { + final ArchiveStreamFactory factory = new ArchiveStreamFactory(charset.name()); + try { + this.out = factory.createArchiveOutputStream(archiverName, targetStream); + } catch (ArchiveException e) { + throw new CompressException(e); + } + + //特殊设置 + if(this.out instanceof TarArchiveOutputStream){ + ((TarArchiveOutputStream)out).setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); + } else if(this.out instanceof ArArchiveOutputStream){ + ((ArArchiveOutputStream)out).setLongFileMode(ArArchiveOutputStream.LONGFILE_BSD); + } + } + + /** + * 将文件或目录加入归档包,目录采取递归读取方式按照层级加入 + * + * @param file 文件或目录 + * @param path 文件或目录的初始路径,null表示位于根路径 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @return this + * @throws IORuntimeException IO异常 + */ + @Override + public StreamArchiver add(File file, String path, FileFilter filter) throws IORuntimeException { + try { + addInternal(file, path, filter); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件 + * + * @return this + */ + @Override + public StreamArchiver finish() { + try { + this.out.finish(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + @Override + public void close() { + try { + finish(); + } catch (Exception ignore) { + //ignore + } + IoUtil.close(this.out); + } + + /** + * 将文件或目录加入归档包,目录采取递归读取方式按照层级加入 + * + * @param file 文件或目录 + * @param path 文件或目录的初始路径,null表示位于根路径 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + */ + private void addInternal(File file, String path, FileFilter filter) throws IOException { + if (null != filter && false == filter.accept(file)) { + return; + } + final ArchiveOutputStream out = this.out; + + final String entryName = StrUtil.addSuffixIfNot(StrUtil.nullToEmpty(path), StrUtil.SLASH) + file.getName(); + out.putArchiveEntry(out.createArchiveEntry(file, entryName)); + + if (file.isDirectory()) { + // 目录遍历写入 + final File[] files = file.listFiles(); + for (File childFile : files) { + addInternal(childFile, entryName, filter); + } + } else { + if (file.isFile()) { + // 文件直接写入 + FileUtil.writeToStream(file, out); + } + out.closeArchiveEntry(); + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/package-info.java new file mode 100644 index 000000000..60482c213 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/package-info.java @@ -0,0 +1,13 @@ +/** + * 基于commons-compress的压缩解压封装
+ * 支持包括:gzip, bzip2, xz, lzma, Pack200, DEFLATE, Brotli, DEFLATE64, ZStandard and Z, the archiver formats are 7z,
+ * ar, arj, cpio, dump, tar and zip等格式。 + * + *

+ * 见:https://commons.apache.org/proper/commons-compress/ + *

+ * + * @author looly + * + */ +package cn.hutool.extra.compress; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java index cc8c0341d..f317c6e3c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java @@ -27,6 +27,15 @@ import java.util.List; */ public class SimpleFtpServer { + /** + * 创建FTP服务器,调用{@link SimpleFtpServer#start()}启动即可 + * + * @return SimpleFtpServer + */ + public static SimpleFtpServer create() { + return new SimpleFtpServer(); + } + FtpServerFactory serverFactory; ListenerFactory listenerFactory; @@ -38,15 +47,6 @@ public class SimpleFtpServer { listenerFactory = new ListenerFactory(); } - /** - * 创建FTP服务器,调用{@link SimpleFtpServer#start()}启动即可 - * - * @return SimpleFtpServer - */ - public static SimpleFtpServer create() { - return new SimpleFtpServer(); - } - /** * 获取 {@link FtpServerFactory},用于设置FTP服务器相关信息 * diff --git a/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java b/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java new file mode 100644 index 000000000..0b0ee0918 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java @@ -0,0 +1,50 @@ +package cn.hutool.extra.compress; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.extra.compress.archiver.StreamArchiver; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; + +public class ArchiverTest { + + @Test + @Ignore + public void tarTest(){ + final File file = FileUtil.file("d:/test/compress/test.tar"); + StreamArchiver.create(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.TAR, file) + .add(FileUtil.file("d:/Java"), (f)->{ + Console.log("Add: {}", f.getPath()); + return true; + }) + .finish().close(); + } + + @Test + @Ignore + public void cpioTest(){ + final File file = FileUtil.file("d:/test/compress/test.cpio"); + StreamArchiver.create(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.CPIO, file) + .add(FileUtil.file("d:/Java"), (f)->{ + Console.log("Add: {}", f.getPath()); + return true; + }) + .finish().close(); + } + + @Test + @Ignore + public void senvenZTest(){ + final File file = FileUtil.file("d:/test/compress/test.7z"); + CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.SEVEN_Z, file) + .add(FileUtil.file("d:/Java"), (f)->{ + Console.log("Add: {}", f.getPath()); + return true; + }) + .finish().close(); + } +}