프로젝트/토이 프로젝트(개인)

[ONLY JAVA-File] 자바로 탐색기 기능을 구현해보자!! / Java Explore

으노으뇨 2022. 12. 2. 19:01
728x90
반응형
SMALL
개요

자바로 리눅스의 커맨드 명령어 처럼 사용할 수 있는 프로그램을 개발하여, JAVA에 내장되어 있는 기본적인 파일 클래스에 대해 알아보고, Java의 Enum 클래스에 대한 전반적인 기능과 응용을 하기위함임. 

  1. 파일 클래스 학습
  2. Java Enum 클래스 응용
  3. 객체지향언어적인 사용을 위한 상속과 오버라이딩 사용
내용

안녕하세요~! ㅎㅎㅎ지난 포스팅으로 간단하게 파일 클래스를 이용해서 간단한 탐색기 기능을 구현해봤어요!

그렇다고 그게 프로그램으로는 조금 부족한 감이있었죠ㅠㅠ

https://uno-kim.tistory.com/243

이번에는 리눅스 기반의 간단한 명령를 통해서 조금더 확장된 기능을 하는 탐색기 프로그램을 만들어 보았습니다!!

explore.zip
0.00MB

우선 실행하는 화면입니다.!!!

순서

  1. 진입
  2. 드라이브 선택
  3. 하위 디렉터리로 이동 
  4. 현재 경로 출력기능
  5. 현재 디렉터리 파일들 전체 출력
  6. 상위로 이동
  7. 루트선택 모드 진입
  8. 다른 드라이브 진입
  9. 다시 루트선택모드 진입
  10. 절대경로를 이용한 디렉터리 진입
  11. 종료
실제 실행하는 화면

주요 기능

  • 리눅스의  cd , ls , pwd , exit 등의 기본 명령어를 사용자가 사용하게끔 하였다.
  • 드라이브별 넘나들도록 설계
  • 파일일 경우 파일임을 사용자에게 알려줌
  • 절대경로 ( / ) 를 붙이면 절대경로에 맞게 디렉터리 이동기능
  • 상대경로를 통해 이동가능

 

구조

구조는 어렵지 않게 구현했으며

중복되는 기능에 대해서는 상수클래스 Enum 클래스로 따로모았습니다.

  • StartClass : 실행부, main메서드존재 - ExploreRun클래스를 선언하고 프로그램 실행유무를 물어본다. 
  • ExploreRun : 간단한 메서드가 있다. 드라이브선택, 탐색기실행, 현재창 출력, 사용자 입력관리 등을 한다.
  • SuperFile : ExploreRun에 상속해준다 원래 겟셋을 해주려했으나 너무 힘들어서...
  • FileInterface : 추상화객체이며 Enum클래스에 상속을 위해 만들었다. 사용자 입력값을 처리하기 위한 클래스
  • KeyEnum : 사용자가 콘솔에 입력하는 값에 따라 각각 고유의 기능을 작동시키도록 작성했다.
  • MessageInterface : 사용자에게 상태출력에 대한기능을 상속해주고자하는 인터페이스
  • MessageEnum : 주로 사용하는 메세지를 상황에 맞게 출력할 수있도록 하는 클래스

소스

소스에 대해서는 너무 많으니 상세히 하기 보다는 사진하나붙여놓고 각 기능별로 특징이나 특성에 대해서 간략하게 설명드리겠습니다.

[메인클래스]

package Java.File.explore;

public class StartClass
{
	public static void main(String[] args)
	{
		ExploreRun er = new ExploreRun();
		er.checkUserPlayDecision();
	}
}

실동작하는 클래스 객체를 선언하고 체크하는 메서드를 실행하여 본격적인 프로그램 실행 시작


[ExmplorRun 클래스]

해당 클래스엔 총 5개의 메서드가 있으며  public 매서드는 1개뿐이다.

사용자가 탐색기 실행에 따른 결정을 물어보는 매서드 : checkUserPlayDecision()
	public void checkUserPlayDecision()
	{
		boolean startFlag = true;
		MessageEnum.Q_PROGRAM_START.message();
		MessageEnum.ETC_USER_INPUT_LINE.message();
		while (startFlag)
		{
			String input = sc.next();
			if (input.equalsIgnoreCase("Y"))
			{
				startFlag = false;
			}
			else if (input.equalsIgnoreCase("N"))
			{
				MessageEnum.N_PROGRAM_END.message();
				super.exit();
			}
			else
			{
				MessageEnum.A_PLEASE_ANSWER_Y_N.message();
			}
		}
		this.play();
	}

입력값에 따라 실행, 종료, 재묻기 기능 수행합니다.

그리고 완료되면 진짜 실행기능인 play() 메서드를 호출하면서 종료됩니다.

실제 실행하는 메서드를 호출하는 매서드 : play()

사용자로부터 입력값에 따른 실행을 위해 따로 분리했다.

상속받은 클래스로부터 우선 초기화를 해주며(크게 의미는없지만...)

그리고 탐색할 드라이브를 고르는 매서드를 호출시킨다. 

	void play()
	{
		super.reset();
		this.selectDrivePath();
	}
사용자가 탐색을 하고싶은 드라이브를 물어보는 매서드 : selectDrivePath()
	/* 1 */void selectDrivePath()
	{
		boolean rootFlag = true;
		File ROOTS[] = File.listRoots();
		while (rootFlag)
		{
			MessageEnum.ETC_DOTS_LARGE.message();
			for (File f : ROOTS)
			{
				System.out.println(MessageEnum.ETC_DOTS_SMALL.DOTS_S + f.getPath() + "  ");
			}
			MessageEnum.Q_SELECT_DRIVE.message();
			MessageEnum.ETC_USER_INPUT_LINE.message();
			String input = sc.next();
			for (int i = 0; i < ROOTS.length; i++)
			{
				String rootPath = ROOTS[i].getPath();
				if (input.equalsIgnoreCase(rootPath) || input.equalsIgnoreCase(rootPath.replace("\\", ""))
						|| input.equalsIgnoreCase(rootPath.replace("\\", "").replace(":", "")))
				{
					super.setRootAndDir(ROOTS[i].getPath());
					rootFlag = false;
				}
			}
		}
		this.openExplore();
	}

해당 메서드가 호출되면 이렇게 출력되며

현재는 C,D가 입력된다. 만약 외장하드를 실행한다면?! 지금 실행중일때 해보겠습니다.

바로 반영된것을 알 수 있습니다 ㅎㅎㅎ

드라이브를 선택하면 이제 해당 드라이브 디렉터리(Root 디렉터리라고하겠습니다.) 에서 탐색할 준비가 완료됩니다.

그래서 해당 기능만! 수행하는 매서드를 호출하면서 종료됩니다.

사용자가 진입한 디렉터리에 대한 정보를 알려주는 매서드 : 

사용자가 드라이브를 진입하면 나타나는 매서드입니다. 이 매서드는 최초 Root디렉터리 진입할때만 나타납니다.

	void dirPrint()
	{
		MessageEnum.ETC_DOTS_LARGE.message();
		System.out.println(MessageEnum.ETC_DOTS_SMALL.DOTS_S + "현재는 디렉터리는 " + super.file.getAbsolutePath() + "::::::");
		MessageEnum.N_NOW_DIRECTORY_LIST.message();
		MessageEnum.ETC_DOTS_LARGE.message();
		for (int i = 0; i < super.file.list().length; i++)
		{
			if (i == 0)
			{
				MessageEnum.ETC_DOTS_SMALL.message();
			}
			System.out.print(super.file.list()[i] + "\t");
			if (i != 0 && i % 3 == 0)
			{
				System.out.println();
				if (i != super.file.list().length - 1)
				{
					MessageEnum.ETC_DOTS_SMALL.message();
				}
			}
		}
		MessageEnum.ETC_DOTS_LARGE.message();
	}

크게 값을 바꾸거나 다루는 것은 없습니다. 해당 메서드에서 출력되는 모습은 다음과 같습니다.

제 C드라이브의 모든것이 적나라하게 나오네요 ㅎㅎㅎ

실제 사용자가 콘솔에 입력하면서 탐색기기능을 이용하는 매서드 : 

드라이브 선택하는 메서드가 종료되면서 이 매서드를 호출합니다. 

	void openExplore()
	{
		dirPrint();
		boolean progressFlag = true;
		while (progressFlag)
		{
			MessageEnum.ETC_USER_INPUT_LINE.message();
			String userInput = extracted().nextLine();
			super.input = userInput;
			for (KeyEnum e : KeyEnum.values())
			{
				if (super.input.split(" ")[0].toUpperCase().equals(e.name()))
				{
					e.keyPress(this);
					super.resetInput();
					if (super.checkRootMode())
					{
						this.selectDrivePath();
						break;
					}
					break;
				}
			}
		}
	}

해당 함수가 호출되면 앞서서 설명한 root디렉터리의 리스트를 출력해주고 

while문으로 진입하게 됩니다.

이제 반복문 내에서 사용자는 키입력으로 이것저것 할 수 있게 되었습니다.

[FileInterface 클래스]

package Java.File.explore;

public interface FileInterface
{
	void keyPress(final ExploreRun pack);
}

클래스만 보았을땐 크게 하는일은 없습니다만 이 클래스를 이넘에 상속시켜 줌으로 엄청난 일을 하게됩니다.

[KeyEnum 클래스] 

package Java.File.explore;

import java.io.File;

public enum KeyEnum implements FileInterface
{
	CD
	{
		@Override
		public void keyPress(ExploreRun pack)
		{
			String dir = pack.input.substring(pack.input.indexOf(" ")).replaceAll(" ", "");
			if (dir.equalsIgnoreCase("root") || dir.equalsIgnoreCase("/root") || dir.equalsIgnoreCase("\\"))
			{
				pack.rootMode = true;
				pack.reset();
			}
			else
			{
				if (dir.charAt(0) == '/')
				{
					this.checkExist(pack.root + dir.replaceFirst("/", ""), pack);
				}
				else if (dir.equals(".."))
				{
					try
					{
						this.checkExist(pack.file.getParent(), pack);
					}
					catch (NullPointerException ne)
					{
						MessageEnum.N_NO_PARENTDIR.message();
					}
				}
				else if (dir.contains("/"))
				{
					this.checkExist(pack.path + dir, pack);
				}
				else
				{
					for (int i = 0; i < pack.file.list().length; i++)
					{
						if (pack.file.list()[i].equals(dir))
						{
							this.checkExist(pack.path + "/" + dir, pack);
							break;
						}
					}
				}
			}
		}
	},
	LS
	{
		@Override
		public void keyPress(ExploreRun pack)
		{
			MessageEnum.ETC_DOTS_LARGE.message();
			MessageEnum.N_NOW_DIRECTORY_LIST.message();
			for (int i = 0; i < pack.file.list().length; i++)
			{
				if (i == 0)
				{
					MessageEnum.ETC_DOTS_SMALL.message();
				}
				System.out.print(pack.file.list()[i] + "\t");
				if (i != 0 && i % 3 == 0)
				{
					System.out.println();
					if (i != pack.file.list().length - 1)
					{
						MessageEnum.ETC_DOTS_SMALL.message();
					}
				}
			}
			MessageEnum.ETC_DOTS_LARGE.message();
		}
	},
	PWD
	{
		@Override
		public void keyPress(ExploreRun pack)
		{
			System.out.println(MessageEnum.ETC_DOTS_SMALL.DOTS_S + "현재는 디렉터리는 " + pack.file.getAbsolutePath() + " 입니다.");
		}
	},
	EXIT
	{
		@Override
		public void keyPress(ExploreRun pack)
		{
			MessageEnum.N_PROGRAM_END.message();
			pack.exit();
		}
	};

	protected void checkExist(String dir, ExploreRun pack)
	{
		File tmpFile = new File(dir);
		if (tmpFile.exists())
		{
			if (tmpFile.isDirectory())
			{
				pack.setDir(tmpFile.getAbsolutePath());
				pack.dirPrint();
			}
			else if (tmpFile.isFile())
			{
				MessageEnum.N_IS_FILE.message();
			}
			else
			{
				MessageEnum.N_NO_DIRECTORYPATH.message();
			}
		}
		else
		{
			MessageEnum.N_NO_DIRECTORYPATH.message();
		}
	}
}

우선은 각 Key별 동작을 이넘으로 구성했습니다.

왜냐하면 계획을 cd, pwd, ls, exit 으로 밖에 안세웠기때문이죠

만약 더 욕심이있어서 파일을 다루고 하는 기능까지 넣게된다면 각 기능을 클래스로 빼서 보관할텐데 너무 커질것같아서 이넘으로 그냥 정리했습니다.

아까 생성한 추상클래스 FileInterface를 상속받았더니 각 상수마다 해당 매서드를 상속받게 되었습니다.

그래서 상수마다 모두 각 매서드를 사용할 수 있게되었습니다.

총 4개의 상수와 하나의 내부 매서드가 있습니다.

cd 명렁어 - Change Diretory의 약자인건 모두아시죠? 그래서 그 기능을 수행하는 매서드입니다.

CD change directory 관련 함수들
@Override
		public void keyPress(ExploreRun pack)
		{
			String dir = pack.input.substring(pack.input.indexOf(" ")).replaceAll(" ", "");
			if (dir.equalsIgnoreCase("root") || dir.equalsIgnoreCase("/root") || dir.equalsIgnoreCase("\\"))
			{
				pack.rootMode = true;
				pack.reset();
			}
			else
			{
				if (dir.charAt(0) == '/')
				{
					this.checkExist(pack.root + dir.replaceFirst("/", ""), pack);
				}
				else if (dir.equals(".."))
				{
					try
					{
						this.checkExist(pack.file.getParent(), pack);
					}
					catch (NullPointerException ne)
					{
						MessageEnum.N_NO_PARENTDIR.message();
					}
				}
				else if (dir.contains("/"))
				{
					this.checkExist(pack.path+ "/" + dir, pack);
				}
				else
				{
					for (int i = 0; i < pack.file.list().length; i++)
					{
						if (pack.file.list()[i].equals(dir))
						{
							this.checkExist(pack.path + "/" + dir, pack);
							break;
						}
					}
				}
			}
		}

cd root , cd /root , cd \root  등 입력된다면 루트디렉터리 선택하는 화면으로 이동시킵니다.

else문의 첫번째 if문은 절대경로로 찾아주는 매서드입니다.

절대경로로 작성하면 해당 위치로 바로갑니다.

그리고 두번째 는 .. 이 들어왔을때 상위 디렉터리로 가게합니다.

이때 만약 상위 디렉터리가 더 없다면 해당 에러를 출력합니다.

cd .. 을 이용해&nbsp; 상위 디렉터리로 이동한 모습
cd .. 를 했지만 root 디렉터리라 더 상위로 못가서 나오는 출력

세번째는 상대경로지만 / 를 포함해서 하위의 하위 디렉터리까지 이동가능하게 합니다.

/와는 다르게 현재 디렉터리에서 상대경로로 찾는다.

 

마지막으로 cd 이후 / 가 없이 폴더나 파일명만 덩그러니 입력한 경우다. 해당 디렉터리의 파일을 모조리 훝어보고 없으면 진행안하고 있으면 해당 폴더나 파일로 이동하게 된다.

LS List Show 현재 디렉터리의 목록을 출력시키는 함수
		@Override
		public void keyPress(ExploreRun pack)
		{
			MessageEnum.ETC_DOTS_LARGE.message();
			MessageEnum.N_NOW_DIRECTORY_LIST.message();
			for (int i = 0; i < pack.file.list().length; i++)
			{
				if (i == 0)
				{
					MessageEnum.ETC_DOTS_SMALL.message();
				}
				System.out.print(pack.file.list()[i] + "\t");
				if (i != 0 && i % 3 == 0)
				{
					System.out.println();
					if (i != pack.file.list().length - 1)
					{
						MessageEnum.ETC_DOTS_SMALL.message();
					}
				}
			}
			MessageEnum.ETC_DOTS_LARGE.message();
		}

크게 기능은 없습니다.

pwd 현재 디렉터리의 경로를 출력하는 함수

		@Override
		public void keyPress(ExploreRun pack)
		{
			System.out.println(MessageEnum.ETC_DOTS_SMALL.DOTS_S + "현재는 디렉터리는 " + pack.file.getAbsolutePath() + " 입니다.");
		}

이또한 크게 설명은 없습니다.

exit 종료함수 exit 이거 국룰이죠
@Override
		public void keyPress(ExploreRun pack){
			MessageEnum.N_PROGRAM_END.message();
			pack.exit();
		}
	protected void checkExist(String dir, ExploreRun pack)
	{
		File tmpFile = new File(dir);
		if (tmpFile.exists())
		{
			if (tmpFile.isDirectory())
			{
				pack.setDir(tmpFile.getAbsolutePath());
				pack.dirPrint();
			}
			else if (tmpFile.isFile())
			{
				MessageEnum.N_IS_FILE.message();
			}
			else
			{
				MessageEnum.N_NO_DIRECTORYPATH.message();
			}
		}
		else
		{
			MessageEnum.N_NO_DIRECTORYPATH.message();
		}
	}

FIle기본 매서드를 이용해서 존재하는지 또 그게 폴더인지, 파일인지 구분하는 로직으로 구현했습니다.

만약 파일일경우

출력하고 폴더일경우 

이렇게 서슴치 않고 들어갑니다.

[SuperFile 부모클래스]

package Java.File.explore;

import java.io.File;
import java.util.ArrayList;
import java.util.Scanner;

public class SuperFile
{
	public File file;
	public String root;
	public String path;
	public String input;
	public boolean rootMode = true;
	public Scanner sc = new Scanner(System.in);
	public ArrayList<String> heap = new ArrayList<String>();

	void setRootAndDir(final String dir)
	{
		this.setRoot(dir);
		this.setDir(dir);
	}

	void setRoot(final String dir)
	{
		this.root = dir;
		this.rootMode = false;
	}

	void setDir(final String dir)
	{
		this.path = dir;
		this.file = new File(dir);
	}

	void resetInput()
	{
		this.input = null;
	}

	void reset()
	{
		this.input = null;
		this.path = null;
		this.file = new File("");
		this.heap.clear();
	}

	void exit()
	{
		this.sc.close();
		System.exit(0);
	}

	boolean checkRootMode()
	{
		return this.rootMode;
	}
}

이 메서드는  ExploreRun 클래스가 진행되는동안 중요한 일을 합니다. 주로 DB? 처럼 운영됩니다.

물론 다 휘발성 값들이긴 하지만요

그래도 이넘클래스와 이곳저것을 넘나들면서도 현재의 위치나 파일, 경로 그리고 루트검색 모드등을 저장하고있다가 제때 반환해줍니다.

이 클래스가 생기면서 지난 포스팅에 만들었던 Heap 은 안쓰게 되었습니다.

물론 새로고침 기능을 넣는다면 필요하겠지요?

[MessageInterface 클래스]

package Java.File.explore;

public interface MessageInterface
{
	void message();
}

이 클래스도 크게 설명드릴것은 없습니다. 이넘클래스에 상속해준다 정도가 되겠군요

[MessageEnum 클래스]

package Java.File.explore;

public enum MessageEnum implements MessageInterface
{
	Q_SELECT_DRIVE
	{
		@Override
		public void message()
		{
			System.out.println("::::::::::::		찾으실 드라이브를 선택해주세요	::::::::::::::");
		}
	},
	Q_PROGRAM_START
	{
		@Override
		public void message()
		{
			System.out.println(this.DOTS_L);
			System.out.println("::::::::::::		파일탐색기를 열으셨습니다.	::::::::::::::");
			System.out.println("::::::::::::		파일탐색을 시작하시겠습니까?	::::::::::::::");
			System.out.println(this.DOTS_L);
		}
	},
	A_PLEASE_ANSWER_Y_N
	{
		@Override
		public void message()
		{
			System.out.println("::::::::::::		Y 또는 N을 눌러서 의사를 명확히 하세요	::::::");
		}
	},
	ETC_DOTS_LARGE
	{
		@Override
		public void message()
		{
			System.out.println(this.DOTS_L);
		}
	},
	ETC_DOTS_SMALL
	{
		@Override
		public void message()
		{
			System.out.print(this.DOTS_S);
		}
	},
	ETC_USER_INPUT_LINE
	{
		@Override
		public void message()
		{
			// TODO Auto-generated method stub
			System.out.print(this.DOTS_S + "User : ");
		}
	},
	N_IS_FILE
	{
		@Override
		public void message()
		{
			System.out.println("::::::::::::		파일을 선택하셨습니다		::::::::::::::");
		}
	},
	N_PROGRAM_END
	{
		@Override
		public void message()
		{
			System.out.println("::::::::::::		프로그램을 종료합니다		::::::::::::::");
		}
	},
	N_NOW_DIRECTORY_LIST
	{
		@Override
		public void message()
		{
			// TODO Auto-generated method stub
			System.out.println("::::::::::::		현재디렉터리 리스트		::::::::::::::");
		}
	},
	N_NO_DIRECTORYPATH
	{
		@Override
		public void message()
		{
			System.out.println("::::::::::::		알 수 없는 경로입니다		::::::::::::::");
		}
	},

	N_NO_PARENTDIR
	{
		@Override
		public void message()
		{
			System.out.println("::::::::::::::::::::::::상위 디렉터리가 없습니다!! ::::::::::::::::::");
			System.out.println(":::Root 선택 모드로 진입하고 싶으시면 cd root 또는 cd /root 을 입력하세요:::");
		}
	},
	N_NOW_DIRECTORY_DIR
	{
		@Override
		public void message()
		{
			// TODO Auto-generated method stub

		}
	};
	final String DOTS_L = "::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::";
	final String DOTS_S = "::::::::::::		";

}

자주사용되는 메세지에 대해서 따로 저장하고 관리되는 상수형 클래스입니다. 만약 해당 부분에서 사용자에게 출력해줘야할 메시지가 있다면

sysout을 또 작성하고 메세지 또는 복사 붙여놓기 하는 시간에 저 소스로 재활용 할 수 있도록 작성했습니다.

그리고 상수형 이름앞에 메세지 상황을 구분지어주었는데

  • Q_ : Question 사용자에게 질문 또는 선택지를 주는 메세지
  • N_ : Notification의 약자로 안내해주는 정도의 메세지
  • ETC_ : :::와 같이 점들을 관리하는 메세지

이렇게 자바로 탐색기기능을 만들어보았습니다.

자바환경으로 리눅스를 다루는 그런 재미가 있었네요 ㅎㅎㅎ

누군가에게 필요한 정보가 될지 모르겠지만 그래도 혼자서 이렇게 프로그램하나 만들어보고 소스다듬어보고 

해보니까 정말 시간가는줄 몰랐네요ㅎㅎㅎ

저도 이제 코딩을 시작한지 얼마 안되었는대 

너무 기초적인 프로그래밍이라 그런가 간단하게 설계되고 구현되었네요

이상으로 제 엉망진창 파일 탐색기 

자바로 파일 탐색기 기능

자바로 파일 탐색

자바로 리눅스 탐색

자바로 리눅스 명령프롬프트 구현하기

이었습니다. 

긴 글 읽어주셔서 감사합니다.

728x90
반응형
LIST