mirror of
https://gitee.com/dromara/hutool.git
synced 2025-10-07 23:24:43 +08:00
add VirtualFile
This commit is contained in:
@@ -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;
|
||||
|
||||
/**
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件
|
||||
*
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user