[SpringUtil + Java] 디렉토리 내의 파일을 특정해서 복사하기

식빵·2023년 12월 23일
0

Java Lab

목록 보기
19/29
post-thumbnail
post-custom-banner

이번에 회사에서 디렉토리 내에서 특정 파일의 prefix확장자
확인하고 복사하는 기능을 만들었다.

별거 아니지만, 그래도 나중에 또 써먹을 거 같아서 기록해둔다.

디렉토리 파일 복사 Code

테스트 코드 작성 환경
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

  • prefix: copy_
  • 확장자 : "txt,csv"

... 인 것만 복사됐네요! 성공입니다!

profile
백엔드를 계속 배우고 있는 개발자입니다 😊
post-custom-banner

0개의 댓글