/*
* Copyright 2011-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kr.debop4j.core.io;
import kr.debop4j.core.parallelism.AsyncTool;
import kr.debop4j.core.tools.StringTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import static kr.debop4j.core.tools.StringTool.listToString;
/**
* 파일 관련 Tool
*
* @author 배성혁 ( sunghyouk.bae@gmail.com )
* @since 12. 12. 11
*/
public class FileTool {
private static final Logger log = LoggerFactory.getLogger(FileTool.class);
private static final boolean isTraceEnabled = log.isTraceEnabled();
private static final boolean isDebugEnabled = log.isDebugEnabled();
/** The constant DEFAULT_BUFFER_SIZE. */
public static final int DEFAULT_BUFFER_SIZE = 4096;
/** The constant UTF8. */
public static final Charset UTF8 = Charset.forName("UTF-8");
private FileTool() { }
/**
* Combine path.
*
* @param base the base
* @param other the other
* @return the path
*/
public static Path combine(String base, String... other) {
return Paths.get(base, other);
}
/**
* Combine path.
*
* @param base the base
* @param others the others
* @return the path
*/
public static Path combine(Path base, String... others) {
Path result = base;
for (String other : others)
result = result.resolve(other);
return result;
}
/**
* Combine path.
*
* @param base the base
* @param other the other
* @return the path
*/
public static Path combine(Path base, Path other) {
return base.resolve(other);
}
/**
* Create directory.
*
* @param dir the dir
* @param attrs the attrs
* @return the path
* @throws IOException the iO exception
*/
public static Path createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
if (isDebugEnabled)
log.debug("디렉토리를 생성합니다. dir=[{}]", dir);
return Files.createDirectory(dir, attrs);
}
/**
* Create directories.
*
* @param dir the dir
* @param attrs the attrs
* @return the path
* @throws IOException the iO exception
*/
public static Path createDirectories(Path dir, FileAttribute<?>... attrs) throws IOException {
return Files.createDirectories(dir, attrs);
}
/**
* Create file.
*
* @param path the path
* @param attrs the attrs
* @return the path
* @throws IOException the iO exception
*/
public static Path createFile(Path path, FileAttribute<?>... attrs) throws IOException {
if (isDebugEnabled)
log.debug("파일 생성. path=[{}], attrs=[{}]", path, listToString(attrs));
return Files.createFile(path, attrs);
}
/**
* Copy void.
*
* @param source the source
* @param target the target
* @throws IOException the iO exception
*/
public static void copy(Path source, Path target) throws IOException {
Files.copy(source, target, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
}
/**
* Copy void.
*
* @param source the source
* @param target the target
* @param options the options
* @throws IOException the iO exception
*/
public static void copy(Path source, Path target, CopyOption... options) throws IOException {
Files.copy(source, target, options);
}
/**
* Copy async.
*
* @param source the source
* @param target the target
* @param options the options
* @return the future
*/
public static Future<Void> copyAsync(final Path source, final Path target, final CopyOption... options) {
return
AsyncTool.startNew(new Callable<Void>() {
@Override
public Void call() throws Exception {
copy(source, target, options);
return null;
}
});
}
/**
* 파일을 이동합니다. @param src the src
*
* @param dst the dst
* @throws IOException the iO exception
*/
public static void move(Path src, Path dst) throws IOException {
if (isTraceEnabled)
log.trace("파일을 이동합니다. src=[{}], dst=[{}]", src, dst);
Files.move(src,
dst,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.COPY_ATTRIBUTES,
StandardCopyOption.REPLACE_EXISTING);
}
/**
* Move void.
*
* @param src the src
* @param dst the dst
* @param options the options
* @throws IOException the iO exception
*/
public static void move(Path src, Path dst, StandardCopyOption... options) throws IOException {
if (isTraceEnabled)
log.trace("파일을 이동합니다. src=[{}], dst=[{}], options=[{}]", src, dst, StringTool.listToString(options));
Files.move(src, dst, options);
}
/**
* Move async.
*
* @param src the src
* @param dst the dst
* @param options the options
* @return the future
*/
public static Future<Void> moveAsync(final Path src, final Path dst, final StandardCopyOption... options) {
if (isTraceEnabled)
log.trace("비동기 방식으로 파일을 이동합니다. src=[{}], dst=[{}], options=[{}]", src, dst, StringTool.listToString(options));
return AsyncTool.startNew(new Callable<Void>() {
@Override
public Void call() throws Exception {
move(src, dst, options);
return null;
}
});
}
/**
* Delete void.
*
* @param path the path
* @throws IOException the iO exception
*/
public static void delete(Path path) throws IOException {
if (isDebugEnabled)
log.debug("디렉토리/파일 삭제. path=[{}]", path);
Files.delete(path);
}
/**
* Delete if exists.
*
* @param path the path
* @throws IOException the iO exception
*/
public static void deleteIfExists(Path path) throws IOException {
if (exists(path))
Files.deleteIfExists(path);
}
/**
* Delete directory.
*
* @param directory the directory
* @param deep the deep
* @throws IOException the iO exception
*/
public static void deleteDirectory(Path directory, boolean deep) throws IOException {
if (isDebugEnabled)
log.debug("directory=[{}] 를 삭제합니다. deep=[{}]", directory, deep);
if (!deep) {
deleteIfExists(directory);
} else {
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
if (isTraceEnabled) log.trace("Directory 삭제 dir=[{}]", dir);
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (isTraceEnabled) log.trace("파일 삭제 file=[{}]", file);
Files.delete(file);
return FileVisitResult.CONTINUE;
}
});
}
}
/**
* Delete directory async.
*
* @param directory the directory
* @param deep the deep
* @return the future
*/
public static Future<Void> deleteDirectoryAsync(final Path directory, final boolean deep) {
if (isTraceEnabled)
log.trace("Directory를 삭제합니다. directory=[{}], deep=[{}]", directory, deep);
return AsyncTool.startNew(new Callable<Void>() {
@Override
public Void call() throws Exception {
deleteDirectory(directory, deep);
return null;
}
});
}
/**
* Exists boolean.
*
* @param path the path
* @param linkOptions the link options
* @return the boolean
*/
public static boolean exists(Path path, LinkOption... linkOptions) {
if (isTraceEnabled)
log.trace("파일 존재를 확인합니다. path=[{}], linkOptions=[{}]", path, listToString(linkOptions));
return Files.exists(path, linkOptions);
}
/**
* Read all bytes.
*
* @param path the path
* @return the byte [ ]
* @throws IOException the iO exception
*/
public static byte[] readAllBytes(Path path) throws IOException {
if (isTraceEnabled)
log.trace("파일로부터 모든 내용을 읽어옵니다. path=[{}]", path);
return Files.readAllBytes(path);
}
/**
* Read all lines.
*
* @param bytes the bytes
* @param charset the charset
* @return the list
*/
public static List<String> readAllLines(byte[] bytes, Charset charset) {
List<String> lines = new ArrayList<String>();
if (bytes == null || bytes.length == 0)
return lines;
try (ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Reader in = new InputStreamReader(is, charset);
BufferedReader br = new BufferedReader(in)) {
while (true) {
String line = br.readLine();
if (line == null)
break;
lines.add(line);
}
return lines;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Read all bytes async.
*
* @param path the path
* @param openOptions the open options
* @return the future
*/
public static Future<byte[]> readAllBytesAsync(final Path path, final OpenOption... openOptions) {
assert path != null;
if (isTraceEnabled)
log.trace("비동기 방식으로 파일 정보를 읽어 byte array로 반환합니다. file=[{}], openOptions=[{}]",
path, StringTool.listToString(openOptions));
return AsyncTool.startNew(new Callable<byte[]>() {
@Override
public byte[] call() throws Exception {
try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, openOptions)) {
ByteBuffer buffer = ByteBuffer.allocate((int) fileChannel.size());
Future<Integer> result = fileChannel.read(buffer, 0);
while (!result.isDone()) {
Thread.sleep(1);
}
buffer.flip();
return buffer.array();
} catch (Exception e) {
log.error("파일 내용을 읽어오는데 실패했습니다.", e);
throw new RuntimeException(e);
}
}
});
}
/**
* Read all lines.
*
* @param path the path
* @return the list
* @throws IOException the iO exception
*/
public static List<String> readAllLines(Path path) throws IOException {
return readAllLines(path, StringTool.UTF8);
}
/**
* Read all lines.
*
* @param path the path
* @param cs the cs
* @return the list
* @throws IOException the iO exception
*/
public static List<String> readAllLines(Path path, Charset cs) throws IOException {
if (isTraceEnabled)
log.trace("파일 내용을 문자열로 읽어드립니다. path=[{}], charset=[{}]", path, cs);
return Files.readAllLines(path, cs);
}
/**
* Read all lines async.
*
* @param path the path
* @return the future
*/
public static Future<List<String>> readAllLinesAsync(final Path path) {
return readAllLinesAsync(path, UTF8, StandardOpenOption.READ);
}
/**
* Read all lines async.
*
* @param path the path
* @param openOptions the open options
* @return the future
*/
public static Future<List<String>> readAllLinesAsync(final Path path, final OpenOption... openOptions) {
return readAllLinesAsync(path, UTF8, openOptions);
}
/**
* Read all lines async.
*
* @param path the path
* @param cs the cs
* @param openOptions the open options
* @return the future
*/
public static Future<List<String>> readAllLinesAsync(final Path path,
final Charset cs,
final OpenOption... openOptions) {
if (isTraceEnabled)
log.trace("파일 내용을 문자열로 읽어드립니다. path=[{}], charset=[{}], openOption=[{}]", path, cs, listToString(openOptions));
return AsyncTool.startNew(new Callable<List<String>>() {
@Override
public List<String> call() throws Exception {
Future<byte[]> readTask = readAllBytesAsync(path, openOptions);
return readAllLines(readTask.get(), cs);
}
});
}
/**
* Write void.
*
* @param target the target
* @param bytes the bytes
* @param openOptions the open options
* @throws IOException the iO exception
*/
public static void write(Path target, byte[] bytes, OpenOption... openOptions) throws IOException {
if (isTraceEnabled)
log.trace("파일에 binary 형태의 정보를 씁니다. target=[{}], openOptions=[{}]", target, listToString(openOptions));
Files.write(target, bytes, openOptions);
}
/**
* Write void.
*
* @param target the target
* @param lines the lines
* @param cs the cs
* @param openOptions the open options
* @throws IOException the iO exception
*/
public static void write(Path target,
Iterable<String> lines,
Charset cs,
OpenOption... openOptions) throws IOException {
if (isTraceEnabled)
log.trace("파일에 텍스트 정보를 씁니다. target=[{}], lines=[{}], charset=[{}], openOptions=[{}]",
target, listToString(lines), cs, listToString(openOptions));
Files.write(target, lines, cs, openOptions);
}
/**
* Write async.
*
* @param target the target
* @param bytes the bytes
* @param openOptions the open options
* @return the future
*/
public static Future<Void> writeAsync(final Path target,
final byte[] bytes,
final OpenOption... openOptions) {
if (isTraceEnabled)
log.trace("비동기 방식으로 데이터를 파일에 씁니다. target=[{}], openOptions=[{}]", target, listToString(openOptions));
return AsyncTool.startNew(new Callable<Void>() {
@Override
public Void call() throws Exception {
try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(target, openOptions)) {
Future<Integer> result = fileChannel.write(ByteBuffer.wrap(bytes), 0);
while (!result.isDone()) {
Thread.sleep(1);
}
return null;
} catch (Exception e) {
log.error("비동기 방식으로 파일에 쓰는 동안 예외가 발생했습니다.", e);
throw new RuntimeException(e);
}
}
});
}
/**
* Write async.
*
* @param target the target
* @param lines the lines
* @param cs the cs
* @param openOptions the open options
* @return the future
*/
public static Future<Void> writeAsync(final Path target,
final Iterable<String> lines,
final Charset cs,
final OpenOption... openOptions) {
String allText = StringTool.join(lines, System.lineSeparator());
return writeAsync(target, cs.encode(allText).array(), openOptions);
}
}