package FileReader;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class GuiFindAll {
final static String LINE_SEPARATOR=System.getProperty("line.separator");
static JTextArea txtSrchResults;
static JFrame f;
static volatile String result;
static JPanel createGUI(){
JPanel pnl=new JPanel();
pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
JPanel pnlTemp=new JPanel();
JLabel lblStartDir=new JLabel("Start directory");
pnlTemp.add(lblStartDir);
final JTextField txtStartDir=new JTextField(30);
pnlTemp.add(txtStartDir);
pnl.add(pnlTemp);
pnlTemp=new JPanel();
JLabel lblSrchText=new JLabel("Search text");
pnlTemp.add(lblSrchText);
lblSrchText.setPreferredSize(lblStartDir.getPreferredSize());
final JTextField txtSrchText=new JTextField(30);
pnlTemp.add(txtSrchText);
pnl.add(pnlTemp);
pnlTemp=new JPanel();
JButton btnSearch=new JButton("Search");
pnlTemp.add(btnSearch);
pnl.add(pnlTemp);
pnlTemp=new JPanel();
txtSrchResults=new JTextArea(20,30);
pnlTemp.add(new JScrollPane(txtSrchResults));
pnl.add(pnlTemp);
ActionListener al;
al=new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final String startDir=txtStartDir.getText();
final String srchText=txtSrchText.getText();
txtSrchResults.setText("");
Runnable r;
r=new Runnable() {
@Override
public void run() {
if(!findAll(new File(startDir),srchText)){
Runnable r=new Runnable() {
@Override
public void run() {
String msg="not a directory";
JOptionPane.showMessageDialog(f, msg);
}
};
EventQueue.invokeLater(r);
}
}
};
new Thread(r).start();
}
};
btnSearch.addActionListener(al);
return pnl;
}
static boolean findAll(File file, String srchText){
File[] files=file.listFiles();
if(files==null)
return false;
for(int i=0;i<files.length;i++)
if(files[i].isDirectory())
findAll(files[i],srchText);
else
if(find(files[i].getPath(),srchText)){
result=files[i].getPath();
Runnable r=new Runnable() {
@Override
public void run() {
txtSrchResults.append(result+LINE_SEPARATOR);
}
};
EventQueue.invokeLater(r);
}
return true;
}
static boolean find(String filename, String srchText){
try(BufferedReader br=new BufferedReader(new FileReader(filename))) {
int ch;
outer_loop:
do{
if((ch=br.read())==-1)
return false;
if(ch==srchText.charAt(0)){
for(int i=1;i<srchText.length();i++){
if((ch=br.read())==-1)
return false;
if(ch!=srchText.charAt(i))
continue outer_loop;
}
return true;
}
}
while(true);
} catch (IOException ioe) {
System.err.println("I/O error : "+ ioe.getMessage());
}
return false;
}
public static void main(String[] args){
Runnable r=new Runnable() {
@Override
public void run() {
f=new JFrame("FindAll");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(createGUI());
f.pack();
f.setResizable(false);
f.setVisible(true);
}
};
EventQueue.invokeLater(r);
}
}
=============
FindAll클래스에서는 createGUI(), findAll(), find() 그리고 main() 클래스 메소드와 함께 여러 개의 클래스 필드를 선언하고 있다. FindAll은 멀티 스레드 애플리케이션이다. 메인 스레드는 main()을 실행만 시키고 FindAll의 GUI는 EDT(event-dispatch thread)에서 실행이 되고 findAll()메소드를 실행하는 워커 스레드를 생성한다.
특정 시점에 스레드는 공유된 변수들 (shared variables)과 커뮤니케이션해야 하는데 이런 커뮤니케이션은 동기화 문제를 발생시킬 수 ㅇ씨다. 이런 동기화 문제를 피하기 위해 위의 예제에서는 volatile result 필드를 생성했으며 final 로컬 변수들을 사용한다.
result 필드를 volatile로 선언했기 때문에 멀티코어 또는 멀티 프로세서 플랫폼 상에서도 각각의 코어 또는 프로세서가 이 필드의 복사본을 갖고 있기 때문에 EDT와 워커 스레드에서 result의 String 참도 값을 확인 할 수 있다. 만일 result가 volatile로 선언되어 있지 않으면 EDT에서는 findAll() 메소드에서 일치하는 문자를 찾았을 때 새로운 String 객체가 할당된 result의 참도를 볼 수 없을 것이다( 싱글 프로세서 또는 싱글 코어 플랫폼에서는 이런 문제가 발새하지 않는다)
위와 같은 이유로 startDir와 srchText 로컬 변수를 가지고 있지만 이 로컬 변수들은 volatile이 아닌 final로 선언되어 있다. 이 러컬 변수들은 final로 선언되어 있어야 검색 버튼의 액션 리스너 내의 java.lang.Runnable로 구현된 익명 클래스에서 접근이 가능하다.
결과적으로 final 필드에 대해서는 volatile 선언이 필요하지 않으며 한 필드에 대해 volatile과 final을 동시에 지정을 할 수는 없다. final 필드는 안전하게 접근할 수는 있지만, 반드시 final 참조 필드를 참조하는 객체가 필요하지는 않다. String 객체는 불변이기 때문에 startDir, srchText 그리고 result에서 String 메소드를 호출해도 문제가 발생하지 않는다.
검색 버튼에서 액션 리스너는 runnable내에서 runnable을 선언하고 있어 코드가 복잡해 보이는데 다음은 이 코드들이 어떻게 동작하는지를 단계별로 소개한다.
1. 사용자가 검색 버튼을 누르면 actionPerformed()메소드가 호출된다.
2. actionPerformed() 메소드에서는 검색을 시작할 디렉터리와 검색할 문자를 텍스트 필드에서 가지고 온 후 새로운 검색 결과를 보여주기 위해 텍스트 영역을 " " 로 초기화 한다. 다음, runnable을 생성하고 EDT에서 워커 스레드를 실행한다.
3. 다음, 작업 스레드는 run()메소드 호출을 통해 runnable을 실행할 것이다.
4. run()에서는 검색을 시작하기 위해 findAll()메소드를 호출한다. 만일 finaAll()이 false를 반환하면 javax.swing.JOptionPane 기반의 다이얼로그 박스를 통해 에러 메시지를 출력하는 새로운 runnable이 생성된다. 워커 스레드는 EDT에 다이얼로그 박스에 보여주기 위해 javax.awt.EventQueue의 invokeLater() 메소드를 실행한다.
다음과 같은 코드를 볼 수 있다.
pnl.setLayout (new BoxLayout (pnl, BoxLayout.Y_AXIS));
이 코드는 컨테이너의 컴포넌트를 배치하기 위해 스윙의 javax.swing.BoxLayout 클래스를 사용하는 것이다. java.awt.GridLayout와는 달리 BoxLayout 은 컴포넌트들에게 같은 크기를 지정하지 않는다. 검색 결과가 많이 반환 될 수 있으므로 텍스트 영역은 스크롤이 가능해야 한다. 하지만 이 컴포넌트는 기본적으로 스크롤을 지원하지 않으므로 스크롤을 지원하기 위해 scrollpane을 추가해야 하는데 javax.swing.JScrollPane 클래스를 사용하여스크롤을 지원할 수 있다.
JScrollPane은 스크롤이 추가될 컴포넌트와 함께 호출되는 생성자를 제공하는데 예를 들어 JScrollPane(Component view) 형식의 생성자를 제공한다. 이와는 대조적으로 AWT의 java.awt.ScrollPane 클래스는 add() 메소드를 사용해 스크롤을 지원할 컴포넌트를 전달한다.
정리
많은 애플리케이션에서 파일에 데이터를 저장하거나 파일로부터 데이터를 읽어 오기 위해 파일시스템과 상호 작용을 한다. 자바의 표준 클래스 라이브러리에서는 File, RandomAccessFile, 스트림 그리고 writer/reader API를 통해 파일 시스템에 접근을 제공한다.
자바는 플랫폼 하부의 파일시스템에 접근할 수 있는 FIle 실제 클래스를 제공하는데 File 인스턴스는 파일 또는 디렉터리의 추상 경로명을 포함하고 있다.
파일들은 읽기/쓰기를 같이 사용할 수 있는 임의 접근 형식으로 오픈될 수 있는데 자바에서는 이런 임의 접근(random access)을 RandomAccessFile 클래스를 통해 지원한다.
자바는 I/O 작업을 수행하기 위해 스트림을 사용한다. 스트림은 임의의 길이를 가지는 바이트들의 시퀸스를 의미한다. 바이트들은 애플리케이션으로부터 출력 스트림을 통해 목적지로 전달되며 소스에서 입력 스트림을 통해 애플리케이션으로 전달된다.
java.io 패키지에서는 OutputStream과 InputStream 추상 클래스로부터 파생된 여러가지 출력 스트림과 입력 스트림을 제공한다. FileOutputStream와 BufferedInputStream이외에도 여러가지 스트림 서브 클래스들이 제공된다.
자바의 스트림 클래스는 바이트의 시퀸스를 스트림하기에 적합하지만 바이트 스트림은 문자 셋과 인코딩을 지원하지 못하기 때문에 문자의 시퀸스를 스트림하기에는 부적합하다. 문자에 대한 스트림이 필요하다면 문자 I/O를 지원하도록 설계된 자바의 쓰기와 읽기 클래스들을 사용해야 하는데 이런 클래스들은 문자 인코딩을 지원한다.
java.io 패키지에서 Writer와 Reader 추상 클래스로부터 파생된 여러가지 쓰기와 읽기 클래스를 제공한다. 대표적인 쓰기, 읽기 클래스로는 OutputStreamWriter, FileWriter, InputStreamReader, FileReader 그리고 BufferedReader 등이 있다.
많은 애플리케이션에서 파일시스템뿐만 아니라 네트워크 그리고 데이터베이스와 상호 작용을 한다.
'Java > Working-level Java' 카테고리의 다른 글
OutputStream (0) | 2015.01.26 |
---|---|
Stream (0) | 2015.01.26 |
Thread 3 (0) | 2015.01.14 |
Thread 2 (0) | 2015.01.14 |
Thread (0) | 2015.01.06 |