이번에 회사에서 디렉토리 내에서 특정 파일의 prefix
와 확장자
를
확인하고 복사하는 기능을 만들었다.
별거 아니지만, 그래도 나중에 또 써먹을 거 같아서 기록해둔다.
테스트 코드 작성 환경
JDK
:Azul 17
Spring version
:6
아래 코드의 목표는 다음과 같습니다.
위 디렉토리 내부에 있는 파일들 중에서
copy_
로 시작되면서,txt,csv
인 것만 복사해가고 싶은 겁니다.지금부터 이걸 가능케 하는 코드를 짜보겠습니다.
참고로 jdk 17 의 record 문법을 약간 씁니다!
record 가 생소하면 가볍게 검색하고 오시기 바랍니다.
package me.dailycode.apachecamel.filecopy;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
public class FileCopyTests {
@Test
void testCopyRecursive() throws IOException {
String destinationPath = "C:/test_data/to";
String sourcePath = "C:/test_data/from";
copyRecursively(
Paths.get(sourcePath),
Paths.get(destinationPath),
new AdditionalOptions("copy_", "txt,csv")
);
}
private void copyRecursively(Path src, Path dest) throws IOException {
copyRecursively(src, dest, new AdditionalOptions("",""));
}
private void copyRecursively(Path src, Path dest, AdditionalOptions options) throws IOException {
// src 로 준 디렉토리 경로 "내부"의 파일들만 검색하기 위해서 고의적으로 1을 줬다.
// 참고로 이렇게 하면 src 디렉토리 하부의 subdirectory 는 뒤져보지 않는다!
// 딱 src 디렉토리의 "파일"만 뒤져본다.
final int MAX_SEARCH_DEPTH = 1;
// 필수값 체크
checkRequiredArguments(src, dest);
// 앞서서 "zip,txt" 처럼 받은 확장자 문자열을 Set 으로 변환시킵니다.
Set<String> setOfFileExtensions = options.getFileExtSet();
// 맨 처음 src 에 대해 접근할 때 디렉토리인지 파일인지를 먼저 알아야겠죠?
// 이를 위해서 Files.readAttributes() 메소드를 호출하는 겁니다.
BasicFileAttributes srcAttr = Files.readAttributes(src, BasicFileAttributes.class);
if (srcAttr.isDirectory()) { // 디렉토리인 지 확인합니다.
//EnumSet.of(FOLLOW_LINKS) // 만약 링크 디렉토리도 타깃으로 잡고 싶다면 이렇게!
// 링크 디렉토리까지는 파일 복사 타깃을 하고 싶지 않다면 이렇게!
Files.walkFileTree(src, EnumSet.noneOf(FileVisitOption.class), MAX_SEARCH_DEPTH, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Files.createDirectories(dest.resolve(src.relativize(dir)));
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// 파일의 Prefix 체크 - 체크 조건(filteringFilePrefix 여부)
if (StringUtils.hasText(options.filteringFilePrefix())) {
// 파일 prefix 가 맞지 않다면? return FileVisitResult.CONTINUE;
String pureFileName = StringUtils.stripFilenameExtension(file.getFileName().toString());
if (!pureFileName.startsWith(options.filteringFilePrefix())) {
return FileVisitResult.CONTINUE;
}
}
// 파일의 확장자 체크 - 체크 조건(setOfFileExtensions 가 비었는지 아닌지 여부)
if (!setOfFileExtensions.isEmpty()) {
String fileAbsolutePath = file.toAbsolutePath().toString();
String filenameExtension = StringUtils.getFilenameExtension(fileAbsolutePath);
// 파일 확장자가 맞지 않다면? return FileVisitResult.CONTINUE;
if (!setOfFileExtensions.contains(filenameExtension)) {
return FileVisitResult.CONTINUE;
}
}
// 만약 위 2개의 조건을 모두 넘어왔다면 Files.copy + fileList.add
Path destFile = dest.resolve(src.relativize(file));
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
}
else if (srcAttr.isRegularFile()) {
// 혹여 단일 파일에 대한 처리도 필요하다면 여기에 작성.
throw new IllegalArgumentException("파일에 대해서는 처리하지 않습니다.");
}
else {
throw new IllegalArgumentException("존재하지 않는 디렉토리입니다");
}
}
private record AdditionalOptions(String filteringFilePrefix, String filteringFileExt) {
public AdditionalOptions {
if (!StringUtils.hasText(filteringFilePrefix)) {
filteringFilePrefix = "";
}
if (!StringUtils.hasText(filteringFileExt)) {
filteringFileExt = "";
}
}
public Set<String> getFileExtSet() {
// comma word to Java Set Instance
Set<String> strings = StringUtils.commaDelimitedListToSet(filteringFileExt);
// 잘못된 확장자 사용을 방지하기 위한 재파싱 과정
return strings.stream()
.map(String::trim) // 일단 양쪽에 있는 공백 지우고~
.filter(s -> !StringUtils.containsWhitespace(s)) // 글자 사이에 공백이 있으면, ignore!
.collect(Collectors.toSet());
}
}
private static void checkRequiredArguments(Path src, Path dest) {
Objects.requireNonNull(src, "Source Path required!");
Objects.requireNonNull(dest, "destination Path required!");
}
}
Source Directory
Destination Directory
copy_
"txt,csv"
... 인 것만 복사됐네요! 성공입니다!