add VirtualFile

This commit is contained in:
Looly
2025-09-09 16:25:52 +08:00
parent 07224772de
commit e1fd530614
9 changed files with 844 additions and 10 deletions

View File

@@ -22,6 +22,7 @@ import cn.hutool.v7.core.util.CharsetUtil;
import cn.hutool.v7.core.util.ObjUtil;
import java.io.File;
import java.io.Serial;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -32,6 +33,7 @@ import java.nio.charset.StandardCharsets;
* @author Looly
*/
public class FileWrapper implements Wrapper<File>, Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**

View File

@@ -492,12 +492,24 @@ public class PathUtil {
* @throws IORuntimeException IO异常
*/
public static BasicFileAttributes getAttributes(final Path path, final boolean isFollowLinks) throws IORuntimeException {
return getAttributes(path, getLinkOptions(isFollowLinks));
}
/**
* 获取文件属性
*
* @param path 文件路径{@link Path}
* @param options {@link LinkOption}
* @return {@link BasicFileAttributes}
* @throws IORuntimeException IO异常
*/
public static BasicFileAttributes getAttributes(final Path path, final LinkOption... options) throws IORuntimeException {
if (null == path) {
return null;
}
try {
return Files.readAttributes(path, BasicFileAttributes.class, getLinkOptions(isFollowLinks));
return Files.readAttributes(path, BasicFileAttributes.class, options);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
@@ -506,15 +518,16 @@ public class PathUtil {
/**
* 获得输入流
*
* @param path Path
* @param path Path
* @param options {@link OpenOption}
* @return 输入流
* @throws IORuntimeException 文件未找到
* @since 4.0.0
*/
public static BufferedInputStream getInputStream(final Path path) throws IORuntimeException {
public static BufferedInputStream getInputStream(final Path path, final OpenOption... options) throws IORuntimeException {
final InputStream in;
try {
in = Files.newInputStream(path);
in = Files.newInputStream(path, options);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
@@ -524,13 +537,14 @@ public class PathUtil {
/**
* 获得一个文件读取器
*
* @param path 文件Path
* @param path 文件Path
* @param options {@link OpenOption}
* @return BufferedReader对象
* @throws IORuntimeException IO异常
* @since 4.0.0
*/
public static BufferedReader getUtf8Reader(final Path path) throws IORuntimeException {
return getReader(path, CharsetUtil.UTF_8);
public static BufferedReader getUtf8Reader(final Path path, final OpenOption... options) throws IORuntimeException {
return getReader(path, CharsetUtil.UTF_8, options);
}
/**
@@ -538,12 +552,13 @@ public class PathUtil {
*
* @param path 文件Path
* @param charset 字符集
* @param options {@link OpenOption}
* @return BufferedReader对象
* @throws IORuntimeException IO异常
* @since 4.0.0
*/
public static BufferedReader getReader(final Path path, final Charset charset) throws IORuntimeException {
return IoUtil.toReader(getInputStream(path), charset);
public static BufferedReader getReader(final Path path, final Charset charset, final OpenOption... options) throws IORuntimeException {
return IoUtil.toReader(getInputStream(path, options), charset);
}
/**

View File

@@ -0,0 +1,110 @@
package cn.hutool.v7.core.io.file;
import cn.hutool.v7.core.io.resource.Resource;
import java.io.File;
import java.io.Serial;
/**
* 虚拟文件类继承自File用于在内存中模拟文件
*/
public class VirtualFile extends File {
@Serial
private static final long serialVersionUID = 1L;
private final Resource content;
/**
* 构造一个虚拟文件
*
* @param pathname 文件路径
* @param content 文件内容
*/
public VirtualFile(final String pathname, final Resource content) {
super(pathname);
this.content = content;
}
/**
* 构造一个虚拟文件
*
* @param parent 父路径
* @param child 子文件名
* @param content 文件内容
*/
public VirtualFile(final String parent, final String child, final Resource content) {
super(parent, child);
this.content = content;
}
/**
* 构造一个虚拟文件
*
* @param parent 父文件
* @param child 子文件名
* @param content 文件内容
*/
public VirtualFile(final File parent, final String child, final Resource content) {
super(parent, child);
this.content = content;
}
/**
* 获取文件内容,{@code null}表示无此文件
*
* @return 文件内容
*/
public Resource getContent() {
return this.content;
}
/**
* 获取文件内容,{@code null}表示无此文件
*
* @return 文件内容
*/
public byte[] getBytes() {
return null != this.content ? this.content.readBytes() : null;
}
@Override
public boolean exists() {
return null != this.content;
}
@Override
public boolean isFile() {
return true;
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public long length() {
return null != this.content ? this.content.size() : 0L;
}
@Override
public boolean canRead() {
return exists();
}
@Override
public boolean canWrite() {
return false;
}
@Override
public boolean canExecute() {
return false;
}
@Override
public long lastModified() {
return System.currentTimeMillis();
}
}

View File

@@ -0,0 +1,303 @@
package cn.hutool.v7.core.io.file;
import cn.hutool.v7.core.io.resource.Resource;
import cn.hutool.v7.core.text.CharUtil;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.core.text.split.SplitUtil;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/**
* 虚拟路径类实现Path接口用于在内存中模拟文件路径
*/
public class VirtualPath implements Path {
private final String path;
private final Resource content;
/**
* 构造一个虚拟路径
*
* @param path 路径字符串
* @param content 路径对应的内容
*/
public VirtualPath(final String path, final Resource content) {
this.path = path;
this.content = content;
}
/**
* 获取路径内容
*
* @return 内容
*/
public Resource getContent() {
return this.content;
}
/**
* 获取文件内容,{@code null}表示无此文件
*
* @return 文件内容
*/
public byte[] getBytes() {
return null != this.content ? this.content.readBytes() : null;
}
@Override
public FileSystem getFileSystem() {
throw new UnsupportedOperationException("VirtualPath does not support FileSystem operations");
}
@Override
public boolean isAbsolute() {
return false;
}
@Override
public Path getRoot() {
return null;
}
@Override
public Path getFileName() {
final int index = path.lastIndexOf(CharUtil.SLASH);
if (index == -1) {
return new VirtualPath(path, content);
}
return new VirtualPath(path.substring(index + 1), content);
}
@Override
public Path getParent() {
final int index = path.lastIndexOf(CharUtil.SLASH);
if (index == -1) {
return null;
}
return new VirtualPath(path.substring(0, index), null);
}
@Override
public int getNameCount() {
if(StrUtil.isEmpty(path)){
// ""表示一个有效名称
return 1;
}
if(StrUtil.equals(path, StrUtil.SLASH)){
// /表示根路径,无名称
return 0;
}
// 根路径不算名称
return StrUtil.count(path, StrUtil.SLASH);
}
@Override
public Path getName(final int index) {
if (index < 0) {
throw new IllegalArgumentException("index must be >= 0");
}
final List<String> parts = SplitUtil.splitTrim(path, StrUtil.SLASH);
if (index >= parts.size()) {
throw new IllegalArgumentException("index exceeds name count");
}
return new VirtualPath(parts.get(index), index == parts.size() - 1 ? content : null);
}
@Override
public Path subpath(final int beginIndex, final int endIndex) {
if (beginIndex < 0 || endIndex <= beginIndex) {
throw new IllegalArgumentException("beginIndex or endIndex is invalid");
}
final List<String> parts = SplitUtil.splitTrim(path, StrUtil.SLASH);
if (endIndex > parts.size()) {
throw new IllegalArgumentException("endIndex exceeds name count");
}
final StringBuilder sb = new StringBuilder();
for (int i = beginIndex; i < endIndex; i++) {
if (!sb.isEmpty()) {
sb.append(CharUtil.SLASH);
}
sb.append(parts.get(i));
}
return new VirtualPath(sb.toString(), endIndex == parts.size() ? content : null);
}
@Override
public boolean startsWith(final Path other) {
if (!(other instanceof final VirtualPath otherPath)) {
return false;
}
return this.path.startsWith(otherPath.path);
}
@Override
public boolean startsWith(final String other) {
return this.path.startsWith(other);
}
@Override
public boolean endsWith(final Path other) {
if (!(other instanceof final VirtualPath otherPath)) {
return false;
}
return this.path.endsWith(otherPath.path);
}
@Override
public boolean endsWith(final String other) {
return this.path.endsWith(other);
}
@Override
public Path normalize() {
return this;
}
@Override
public Path resolve(final Path other) {
if (other.isAbsolute()) {
return other;
}
if (other.toString().isEmpty()) {
return this;
}
final String newPath = this.path + "/" + other;
return new VirtualPath(newPath, other instanceof VirtualPath ? ((VirtualPath) other).content : null);
}
@Override
public Path resolve(final String other) {
if (other.isEmpty()) {
return this;
}
final String newPath = this.path + StrUtil.SLASH + other;
return new VirtualPath(newPath, null);
}
@Override
public Path resolveSibling(final Path other) {
if (other == null) {
throw new NullPointerException("other cannot be null");
}
final Path parent = getParent();
return (parent == null) ? other : parent.resolve(other);
}
@Override
public Path resolveSibling(final String other) {
if (other == null) {
throw new NullPointerException("other cannot be null");
}
final Path parent = getParent();
return (parent == null) ? new VirtualPath(other, null) : parent.resolve(other);
}
@Override
public Path relativize(final Path other) {
if (!(other instanceof final VirtualPath otherPath)) {
throw new IllegalArgumentException("other must be a VirtualPath");
}
if (this.path.isEmpty()) {
return otherPath;
}
if (otherPath.path.startsWith(this.path + "/")) {
return new VirtualPath(otherPath.path.substring(this.path.length() + 1), otherPath.content);
}
return otherPath;
}
@Override
public URI toUri() {
throw new UnsupportedOperationException("VirtualPath does not support URI conversion");
}
@Override
public Path toAbsolutePath() {
return this;
}
@Override
public Path toRealPath(final LinkOption... options) {
return this;
}
@Override
public File toFile() {
return new VirtualFile(path, content);
}
@Override
public WatchKey register(final WatchService watcher, final WatchEvent.Kind<?>[] events, final WatchEvent.Modifier... modifiers) throws IOException {
throw new UnsupportedOperationException("VirtualPath does not support watch service");
}
@Override
public WatchKey register(final WatchService watcher, final WatchEvent.Kind<?>... events) throws IOException {
throw new UnsupportedOperationException("VirtualPath does not support watch service");
}
@Override
public Iterator<Path> iterator() {
return new Iterator<>() {
private int index = 0;
private final List<String> parts = SplitUtil.splitTrim(path, StrUtil.SLASH);
@Override
public boolean hasNext() {
return index < parts.size();
}
@Override
public Path next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return new VirtualPath(parts.get(index++), index == parts.size() ? content : null);
}
};
}
@Override
public int compareTo(final Path other) {
if (!(other instanceof final VirtualPath otherPath)) {
throw new ClassCastException("Cannot compare VirtualPath with " + other.getClass().getName());
}
return this.path.compareTo(otherPath.path);
}
@Override
public boolean equals(final Object other) {
if (this == other) {
return true;
}
if (!(other instanceof final VirtualPath otherPath)) {
return false;
}
return this.path.equals(otherPath.path);
}
@Override
public int hashCode() {
return path.hashCode();
}
@Override
public String toString() {
return path;
}
}

View File

@@ -0,0 +1,75 @@
package cn.hutool.v7.core.io.file;
import cn.hutool.v7.core.io.resource.BytesResource;
import org.junit.jupiter.api.Test;
import java.io.File;
import static org.junit.jupiter.api.Assertions.*;
public class VirtualFileTest {
@Test
public void testConstructorWithParentChildAndContent() {
final byte[] content = "Hello World".getBytes();
final VirtualFile virtualFile = new VirtualFile("/tmp", "test.txt", new BytesResource(content));
assertEquals("/tmp", virtualFile.getParent().replace(File.separator, "/"));
assertEquals("test.txt", virtualFile.getName());
assertArrayEquals(content, virtualFile.getBytes());
}
@Test
public void testConstructorWithStringAndContent() {
final byte[] content = "Hello World".getBytes();
final VirtualFile virtualFile = new VirtualFile("/tmp/test.txt", new BytesResource(content));
assertEquals("/tmp/test.txt", virtualFile.getPath().replace(File.separator, "/"));
assertArrayEquals(content, virtualFile.getBytes());
}
@Test
public void testConstructorWithFileParentAndContent() {
final File parent = new File("/tmp");
final byte[] content = "Hello World".getBytes();
final VirtualFile virtualFile = new VirtualFile(parent, "test.txt", new BytesResource(content));
assertEquals("/tmp", virtualFile.getParent().replace(File.separator, "/"));
assertEquals("test.txt", virtualFile.getName());
assertArrayEquals(content, virtualFile.getBytes());
}
@Test
public void testFileProperties() {
final byte[] content = "Hello World".getBytes();
final VirtualFile virtualFile = new VirtualFile("/tmp/test.txt", new BytesResource(content));
assertTrue(virtualFile.exists());
assertTrue(virtualFile.isFile());
assertFalse(virtualFile.isDirectory());
assertEquals(content.length, virtualFile.length());
assertTrue(virtualFile.canRead());
assertFalse(virtualFile.canWrite());
assertFalse(virtualFile.canExecute());
assertTrue(virtualFile.lastModified() > 0);
}
@Test
public void testContentImmutability() {
final byte[] originalContent = "Hello World".getBytes();
final VirtualFile virtualFile = new VirtualFile("/tmp/test.txt", new BytesResource(originalContent));
final byte[] contentFromMethod = virtualFile.getBytes();
// 修改获取到的内容不应该影响原始内容
contentFromMethod[0] = 'h';
// 重新获取内容,应该和原始内容一致
final byte[] newContentFromMethod = virtualFile.getBytes();
assertArrayEquals(originalContent, newContentFromMethod);
}
@Test
public void testNullContent() {
final VirtualFile virtualFile = new VirtualFile("/tmp/test.txt", null);
assertNull(virtualFile.getBytes());
}
}

View File

@@ -0,0 +1,206 @@
package cn.hutool.v7.core.io.file;
import cn.hutool.v7.core.io.resource.BytesResource;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import static org.junit.jupiter.api.Assertions.*;
/**
* VirtualPath单元测试
*/
public class VirtualPathTest {
@Test
public void testConstructor() {
final byte[] content = "Hello World".getBytes();
final VirtualPath virtualPath = new VirtualPath("/tmp/test.txt", new BytesResource(content));
assertEquals("/tmp/test.txt", virtualPath.toString());
assertArrayEquals(content, virtualPath.getBytes());
}
@Test
public void testGetFileName() {
final byte[] content = "Hello World".getBytes();
final VirtualPath virtualPath = new VirtualPath("/tmp/test.txt", new BytesResource(content));
final Path fileName = virtualPath.getFileName();
assertEquals("test.txt", fileName.toString());
assertArrayEquals(content, ((VirtualPath) fileName).getBytes());
}
@Test
public void testGetParent() {
final VirtualPath virtualPath = new VirtualPath("/tmp/subdir/test.txt", null);
final Path parent = virtualPath.getParent();
assertEquals("/tmp/subdir", parent.toString());
assertNull(((VirtualPath) parent).getContent());
}
@Test
public void testGetNameCount() {
final String pathStr = "/tmp/subdir/test.txt";
final VirtualPath virtualPath = new VirtualPath(pathStr, null);
assertEquals(Paths.get(pathStr).getNameCount(), virtualPath.getNameCount());
final VirtualPath rootPath = new VirtualPath("/", null);
assertEquals(Paths.get("/").getNameCount(), rootPath.getNameCount());
final VirtualPath emptyPath = new VirtualPath("", null);
assertEquals(Paths.get("").getNameCount(), emptyPath.getNameCount());
}
@Test
public void testGetName() {
final byte[] content = "Hello World".getBytes();
final VirtualPath virtualPath = new VirtualPath("/tmp/subdir/test.txt", new BytesResource(content));
final Path name0 = virtualPath.getName(0);
assertEquals("tmp", name0.toString());
assertNull(((VirtualPath) name0).getBytes());
final Path name1 = virtualPath.getName(1);
assertEquals("subdir", name1.toString());
assertNull(((VirtualPath) name1).getBytes());
final Path name2 = virtualPath.getName(2);
assertEquals("test.txt", name2.toString());
assertArrayEquals(content, ((VirtualPath) name2).getBytes());
}
@Test
public void testSubpath() {
final VirtualPath virtualPath = new VirtualPath("/tmp/subdir/test.txt", null);
final Path subpath = virtualPath.subpath(1, 3);
assertEquals("subdir/test.txt", subpath.toString());
final Path single = virtualPath.subpath(0, 1);
assertEquals("tmp", single.toString());
}
@Test
public void testStartsWith() {
final VirtualPath virtualPath = new VirtualPath("/tmp/subdir/test.txt", null);
assertTrue(virtualPath.startsWith("/tmp"));
assertTrue(virtualPath.startsWith(new VirtualPath("/tmp", null)));
assertFalse(virtualPath.startsWith("/home"));
}
@Test
public void testEndsWith() {
final VirtualPath virtualPath = new VirtualPath("/tmp/subdir/test.txt", null);
assertTrue(virtualPath.endsWith("test.txt"));
assertTrue(virtualPath.endsWith(new VirtualPath("test.txt", null)));
assertFalse(virtualPath.endsWith("config.txt"));
}
@Test
public void testResolve() {
final VirtualPath virtualPath = new VirtualPath("/tmp", null);
final Path resolved = virtualPath.resolve("test.txt");
assertEquals("/tmp/test.txt", resolved.toString());
final Path resolvedPath = virtualPath.resolve(new VirtualPath("subdir/test.txt", new BytesResource("content".getBytes())));
assertEquals("/tmp/subdir/test.txt", resolvedPath.toString());
}
@Test
public void testResolveSibling() {
final VirtualPath virtualPath = new VirtualPath("/tmp/test.txt", null);
final Path resolved = virtualPath.resolveSibling("config.txt");
assertEquals("/tmp/config.txt", resolved.toString());
}
@Test
public void testRelativize() {
final VirtualPath path1 = new VirtualPath("/tmp/subdir", null);
final VirtualPath path2 = new VirtualPath("/tmp/subdir/test.txt", null);
final Path relativized = path1.relativize(path2);
assertEquals("test.txt", relativized.toString());
}
@Test
public void testToFile() {
final byte[] content = "Hello World".getBytes();
final VirtualPath virtualPath = new VirtualPath("/tmp/test.txt", new BytesResource( content));
final File file = virtualPath.toFile();
assertInstanceOf(VirtualFile.class, file);
assertEquals("/tmp/test.txt", file.getPath().replace(File.separator, "/"));
assertArrayEquals(content, ((VirtualFile) file).getBytes());
}
@Test
public void testIterator() {
final VirtualPath virtualPath = new VirtualPath("/tmp/subdir/test.txt", new BytesResource("content".getBytes()));
final Iterator<Path> iterator = virtualPath.iterator();
assertTrue(iterator.hasNext());
assertEquals("tmp", iterator.next().toString());
assertTrue(iterator.hasNext());
assertEquals("subdir", iterator.next().toString());
assertTrue(iterator.hasNext());
final Path last = iterator.next();
assertEquals("test.txt", last.toString());
assertArrayEquals("content".getBytes(), ((VirtualPath) last).getBytes());
assertFalse(iterator.hasNext());
}
@Test
public void testCompareTo() {
final VirtualPath path1 = new VirtualPath("/tmp/a.txt", null);
final VirtualPath path2 = new VirtualPath("/tmp/b.txt", null);
assertTrue(path1.compareTo(path2) < 0);
assertTrue(path2.compareTo(path1) > 0);
assertEquals(0, path1.compareTo(new VirtualPath("/tmp/a.txt", null)));
}
@Test
public void testEqualsAndHashCode() {
final VirtualPath path1 = new VirtualPath("/tmp/test.txt", null);
final VirtualPath path2 = new VirtualPath("/tmp/test.txt", null);
final VirtualPath path3 = new VirtualPath("/tmp/config.txt", null);
assertEquals(path1, path2);
assertNotEquals(path1, path3);
assertEquals(path1.hashCode(), path2.hashCode());
}
@Test
public void testContentImmutability() {
final byte[] originalContent = "Hello World".getBytes();
final VirtualPath virtualPath = new VirtualPath("/tmp/test.txt", new BytesResource(originalContent));
final byte[] contentFromMethod = virtualPath.getBytes();
// 修改获取到的内容不应该影响原始内容
contentFromMethod[0] = 'h';
// 重新获取内容,应该和原始内容一致
final byte[] newContentFromMethod = virtualPath.getBytes();
assertArrayEquals(originalContent, newContentFromMethod);
}
@Test
public void testNullContent() {
final VirtualPath virtualPath = new VirtualPath("/tmp/test.txt", null);
assertNull(virtualPath.getContent());
}
}

View File

@@ -18,6 +18,8 @@ package cn.hutool.v7.extra.compress.archiver;
import java.io.Closeable;
import java.io.File;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -73,6 +75,21 @@ public interface Archiver extends Closeable {
*/
Archiver add(File file, String path, Function<String, String> fileNameEditor, Predicate<File> filter);
/**
* 将文件或目录加入归档包,目录采取递归读取方式按照层级加入
*
* @param file 文件或目录
* @param path 文件或目录的初始路径null表示位于根路径
* @param fileNameEditor 文件名编辑器
* @param filter 文件过滤器,指定哪些文件或目录可以加入,{@link Predicate#test(Object)}为{@code true}保留null表示全部加入
* @param options 链接选项
* @return this
* @since 7.0.0
*/
default Archiver add(final Path file, final String path, final Function<String, String> fileNameEditor, final Predicate<Path> filter, final LinkOption... options){
return add(file.toFile(), path, fileNameEditor, (f-> filter.test(f.toPath())));
}
/**
* 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件
*

View File

@@ -25,11 +25,13 @@ import cn.hutool.v7.extra.compress.CompressUtil;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -106,6 +108,16 @@ public class SevenZArchiver implements Archiver {
return this;
}
@Override
public SevenZArchiver add(final Path file, final String path, final Function<String, String> fileNameEditor, final Predicate<Path> filter, final LinkOption... options) {
try {
addInternal(file, path, fileNameEditor, filter, options);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return this;
}
@Override
public SevenZArchiver finish() {
try {
@@ -116,6 +128,7 @@ public class SevenZArchiver implements Archiver {
return this;
}
@SuppressWarnings("resource")
@Override
public void close() {
try {
@@ -161,7 +174,48 @@ public class SevenZArchiver implements Archiver {
} else {
if (file.isFile()) {
// 文件直接写入
out.write(FileUtil.readBytes(file));
try (final BufferedInputStream in = FileUtil.getInputStream(file)) {
out.write(in);
}
//out.write(FileUtil.readBytes(file));
}
out.closeArchiveEntry();
}
}
/**
* 将文件或目录加入归档包,目录采取递归读取方式按照层级加入
*
* @param file 文件或目录
* @param path 文件或目录的初始路径null表示位于根路径
* @param fileNameEditor 文件名编辑器
* @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Predicate#test(Object)}为{@code true}保留null表示保留全部
* @param options 链接选项
*/
private void addInternal(final Path file, final String path, final Function<String, String> fileNameEditor, final Predicate<Path> filter, final LinkOption... options) throws IOException {
if (null != filter && !filter.test(file)) {
return;
}
final SevenZOutputFile out = this.sevenZOutputFile;
final String entryName = CompressUtil.getEntryName(PathUtil.getName(file), path, fileNameEditor);
out.putArchiveEntry(out.createArchiveEntry(file, entryName));
if (PathUtil.isDirectory(file)) {
// 目录遍历写入
final Path[] files = PathUtil.listFiles(file, null);
if (ArrayUtil.isNotEmpty(files)) {
for (final Path childFile : files) {
addInternal(childFile, entryName, fileNameEditor, filter);
}
}
} else {
if (Files.isRegularFile(file, options)) {
// 文件直接写入
try (final BufferedInputStream in = PathUtil.getInputStream(file)) {
out.write(in);
}
//out.write(FileUtil.readBytes(file));
}
out.closeArchiveEntry();
}

View File

@@ -16,12 +16,14 @@
package cn.hutool.v7.extra.compress.archiver;
import cn.hutool.v7.core.io.file.PathUtil;
import cn.hutool.v7.extra.compress.CompressUtil;
import org.apache.commons.compress.archivers.ArchiveEntry;
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.sevenz.SevenZOutputFile;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import cn.hutool.v7.core.array.ArrayUtil;
@@ -31,10 +33,14 @@ import cn.hutool.v7.core.io.file.FileUtil;
import cn.hutool.v7.core.text.StrUtil;
import cn.hutool.v7.extra.compress.CompressException;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -132,6 +138,16 @@ public class StreamArchiver implements Archiver {
return this;
}
@Override
public StreamArchiver add(final Path file, final String path, final Function<String, String> fileNameEditor, final Predicate<Path> filter, final LinkOption... options) {
try {
addInternal(file, path, fileNameEditor, filter, options);
} catch (final IOException e) {
throw new IORuntimeException(e);
}
return this;
}
/**
* 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件
*
@@ -195,4 +211,40 @@ public class StreamArchiver implements Archiver {
out.closeArchiveEntry();
}
}
/**
* 将文件或目录加入归档包,目录采取递归读取方式按照层级加入
*
* @param file 文件或目录
* @param path 文件或目录的初始路径null表示位于根路径
* @param fileNameEditor 文件名编辑器
* @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Predicate#test(Object)}为{@code true}保留null表示保留全部
* @param options 链接选项
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private void addInternal(final Path file, final String path, final Function<String, String> fileNameEditor, final Predicate<Path> filter, final LinkOption... options) throws IOException {
if (null != filter && !filter.test(file)) {
return;
}
final ArchiveOutputStream out = this.out;
final String entryName = CompressUtil.getEntryName(PathUtil.getName(file), path, fileNameEditor);
out.putArchiveEntry(out.createArchiveEntry(file, entryName));
if (PathUtil.isDirectory(file)) {
// 目录遍历写入
final Path[] files = PathUtil.listFiles(file, null);
if (ArrayUtil.isNotEmpty(files)) {
for (final Path childFile : files) {
addInternal(childFile, entryName, fileNameEditor, filter);
}
}
} else {
if (Files.isRegularFile(file, options)) {
// 文件直接写入
PathUtil.copy(file, out);
}
out.closeArchiveEntry();
}
}
}