/*
 * Torutk Learning Project for Swing Application Framework.
 *
 * $Id: FileSearchApplication.java 103 2007-10-08 13:23:36Z toru $
 */
import org.jdesktop.application.Action;
import org.jdesktop.application.Application;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.application.Task.BlockingScope;
import org.jdesktop.application.Task;
import org.jdesktop.application.TaskEvent;
import org.jdesktop.application.TaskListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
import javax.swing.ActionMap;
import javax.swing.DefaultListModel;
import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.LayoutStyle;

/**
 * JSR-296 Swing Application Frameworjを使用したファイル検索プログラム。
 * 検索したいファイル名を入力し検索を実行すると、ファイル名に完全一致した
 * ファイルの絶対パスを表示する。<p>
 * 本プログラムの開発・実行条件
 * <li>JDKバージョン： Java SE 6
 * <li>JSR-296バージョン： 0.50
 */
public class FileSearchApplication extends SingleFrameApplication {

    @Override protected void startup() {
        LOGGER.entering(FileSearchApplication.class.getName(), "startup");
        JComponent component = createUI();
        show(component);
        LOGGER.exiting(FileSearchApplication.class.getName(), "startup");
    }

    private JComponent createUI() {
        LOGGER.entering(FileSearchApplication.class.getName(), "createUI");
        JPanel panel = new JPanel();
        panel.setName("myPanel");

        GroupLayout layout = new GroupLayout(panel);
        panel.setLayout(layout);

        //部品間に若干の隙間を自動で作成
        layout.setAutoCreateGaps(true);
        layout.setAutoCreateContainerGaps(true);

        titleLabel = new JLabel();
        titleLabel.setName("titleLabel");

        JLabel nameLabel = new JLabel();
        nameLabel.setName("nameLabel");

        nameField = new JTextField();
        nameField.setName("nameField");

        fileListModel = new DefaultListModel();
        fileList = new JList(fileListModel);
        JScrollPane fileListPane = new JScrollPane(fileList);

        searchButton = new JButton();
        searchButton.setName("searchButton");

        JButton terminateButton = new JButton();
        terminateButton.setName("terminateButton");

        // 水平方向のレイアウト・グループ設定
        GroupLayout.ParallelGroup hGroup = layout.createParallelGroup();
        // タイトル用ラベルは水平方向にリサイズ可能設定
        hGroup.addComponent(
            titleLabel, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE,
            480//Short.MAX_VALUE
        );
        // ファイル名ラベルとテキストフィールドは水平方向順番並び
        hGroup.addGroup(layout.createSequentialGroup()
                        .addComponent(nameLabel)
                        .addComponent(nameField)
        );
        // ファイル一覧は水平方向単独
        hGroup.addComponent(fileListPane);

        // 検索ボタンと終了ボタンは水平方向順番並び、ただし間隔は最大限空ける
        hGroup.addGroup(layout.createSequentialGroup()
                        .addComponent(searchButton)
                        .addPreferredGap(
                            LayoutStyle.ComponentPlacement.RELATED,
                            120, Short.MAX_VALUE
                        )
                        .addComponent(terminateButton)
        );
        layout.setHorizontalGroup(hGroup);

        // 検索ボタンと終了ボタンの水平サイズを同じサイズにする
        layout.linkSize(searchButton, terminateButton);

        // 垂直方向のレイアウト・グループ設定
        GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
        
        vGroup.addComponent(titleLabel);
        // ファイル名ラベルとファイル名入力フィールドの横並びを揃える
        vGroup.addGroup(
            layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
            .addComponent(nameLabel)
            .addComponent(nameField)
        );
        // ファイル一覧は垂直方向単独
        vGroup.addComponent(fileListPane);
        // 検索ボタンと終了ボタンの横並びを揃える
        vGroup.addGroup(
            layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
            .addComponent(searchButton)
            .addComponent(terminateButton)
        );
        layout.setVerticalGroup(vGroup);

        // アクションの設定
        // 検索ボタンのクリックで"search"アクション発動
        searchButton.setAction(getAction("search"));
        // ファイル名入力フィールドの[Enter]キーで"search"アクション発動
        nameField.setAction(getAction("search"));
        // 終了ボタンのクリックで"terminate"アクション発動
        terminateButton.setAction(getAction("terminate"));
        LOGGER.exiting(FileSearchApplication.class.getName(), "createUI", panel);
        return panel;
    }

    /**
     * アクション取得処理をメソッド化。
     */
    private javax.swing.Action getAction(final String anActionName) {
        LOGGER.entering(
            FileSearchApplication.class.getName(), "getAction", anActionName
        );
        ActionMap map = getContext().getActionMap();
        javax.swing.Action action = map.get(anActionName);
        LOGGER.exiting(
            FileSearchApplication.class.getName(), "getAction", action
        );
        return action;
    }

    @Action(block=BlockingScope.ACTION) public Task search() {
        LOGGER.entering(FileSearchApplication.class.getName(), "search");
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        int ret = fileChooser.showOpenDialog(getMainFrame());
        File path = null;
        if (ret == JFileChooser.APPROVE_OPTION) {
            path = fileChooser.getSelectedFile();
        }
        Task task = new FileSearchTask(this, path, nameField.getText());
        task.addTaskListener(new FileSearchTaskListener());
        LOGGER.exiting(FileSearchApplication.class.getName(), "search", task);
        return task;
    }

    @Action public void terminate() {
        LOGGER.entering(FileSearchApplication.class.getName(), "termintate");
        exit();
        LOGGER.exiting(FileSearchApplication.class.getName(), "termintate");
    }

    public static final void main(final String[] args) {
        LOGGER.entering(FileSearchApplication.class.getName(), "main", args);
        launch(FileSearchApplication.class, args);
        LOGGER.exiting(FileSearchApplication.class.getName(), "main");
    }
    
    /*
     * FileSearchTaskの状態遷移をイベントとして受け取り、状態遷移に
     * 対応する処理を行うFileSearchTaskListener実装クラス。
     * 以下のイベントに対する処理を記述している。<p>
     * <li>検索が開始される(直前)
     * <li>検索中に見つかったファイルを途中経過として<code>publish</code>
     *     された
     * <li>検索が終了した
     */
    class FileSearchTaskListener extends TaskListener.Adapter<List<File>, File> {
        /**
         * TaskのdoInBackgroundが呼ばれる前に実行される。
         * ラベルの文字を変更し、ファイル一覧をクリアする。
         */ 
        @Override public void doInBackground(TaskEvent<Void> event) {
            LOGGER.entering(
                FileSearchApplication.FileSearchTaskListener.class.getName(),
                "doInBackground", event
            );
            LOGGER.finest("current thread is : " + Thread.currentThread());
            Task task = (Task)event.getSource();
            titleLabel.setText(task.getMessage());
            fileListModel.clear();
            LOGGER.exiting(
                FileSearchApplication.FileSearchTaskListener.class.getName(),
                "doInBackground"
            );
        }

        /**
         * Taskで途中随時publishされたときに実行される。
         * ファイル名をリストに追加する。
         */
        @Override public void process(TaskEvent<List<File>> event) {
            LOGGER.entering(
                FileSearchApplication.FileSearchTaskListener.class.getName(),
                "process", event
            );
            List<File> files = event.getValue();
            for (File file : files) {
                fileListModel.addElement(file);
            }
            LOGGER.exiting(
                FileSearchApplication.FileSearchTaskListener.class.getName(),
                "process"
            );
         }

        /**
         * Taskが成功したときに実行される。
         * 
         */
        @Override public void succeeded(TaskEvent<List<File>> event) {
            LOGGER.entering(
                FileSearchApplication.FileSearchTaskListener.class.getName(),
                "succeeded", event
            );
            JOptionPane.showMessageDialog(
                getMainFrame(),
                getContext().getResourceMap().getString("succeededMessage")
            );
            // 結果を使用していないが、Taskから結果を取得するコードの例示。
            List<File> finds = event.getValue();
            LOGGER.fine(Arrays.toString(finds.toArray(new File[0])));

            LOGGER.exiting(
                FileSearchApplication.FileSearchTaskListener.class.getName(),
                "succeeded"
            );
        }

        /**
         * Taskが成功/中断/失敗いずれにせよ終了したときに実行される。
         * ラベルの文字を変更する。
         */
        @Override public void finished(TaskEvent<Void> event) {
            LOGGER.entering(
                FileSearchApplication.FileSearchTaskListener.class.getName(),
                "finished", event
            );
            Task task = (Task)event.getSource();
            titleLabel.setText(task.getMessage());
            LOGGER.exiting(
                FileSearchApplication.FileSearchTaskListener.class.getName(),
                "finished"
            );
         }
    }

    private JLabel titleLabel;
    private JTextField nameField;
    private JButton searchButton;
    private JList fileList;
    private DefaultListModel fileListModel;

    private static final Logger LOGGER = Logger.getLogger(
        FileSearchApplication.class.getName()
    );
}

/**
 * バックグラウンドでファイルを検索するタスク。
 *
 * 時間のかかる処理はdoInBackgroundメソッドに実装する。
 */
class FileSearchTask extends Task<List<File>, File> {
    /**
     * Task専用のリソースを使用するコンストラクタ。
     *
     * @param anApplication このタスクを利用するアプリケーション
     * @param aRoot 検索開始場所
     * @param aName 検索対象ファイル名
     */
    FileSearchTask(Application anApplication, File aRoot, String aName) {
        super(anApplication);
        LOGGER.entering(
            FileSearchTask.class.getName(), "FileSearchTask",
            new Object[] {anApplication, aRoot, aName}
        );
        startDirectory = aRoot;
        name = aName;
        findFiles = new ArrayList<File>();
        LOGGER.exiting(FileSearchTask.class.getName(), "FileSearchTask");
    }

    @Override
    protected List<File> doInBackground() throws InterruptedException {
        LOGGER.entering(FileSearchTask.class.getName(), "doInBackground");
        LOGGER.finest("current thread is : " + Thread.currentThread());
        message("startMessage", name);
        findFiles.clear();
        boolean isFind = searchFile(startDirectory);
        message("finishedMessage", isFind);
        LOGGER.exiting(FileSearchTask.class.getName(), "doInBackground", isFind);
        return findFiles;
    }

    private boolean searchFile(File aPath) {
        LOGGER.entering(FileSearchTask.class.getName(), "searchFile", aPath);
        boolean isFind = false;
        try {
            if (aPath == null) {
                isFind = false;
                return isFind;
            } else if (aPath.isFile()) {
                LOGGER.fine(aPath.getName() + ".equals(" + name + ")");
                if (aPath.getName().equals(name)) {
                    findFiles.add(aPath);
                    publish(aPath);
                    isFind = true;
                } else {
                    isFind = false;
                }
                return isFind;
            } else {
                File[] files = aPath.listFiles();
                for (File file : files) {
                    if (searchFile(file)) {
                        isFind = true;
                    }
                }
            }
            return isFind;
        } finally {
            LOGGER.exiting(FileSearchTask.class.getName(), "searchFile", isFind);
        }
    }

    private final File startDirectory;
    private final String name;
    private List<File> findFiles;
    private static final Logger LOGGER = Logger.getLogger(
        FileSearchTask.class.getName()
    );
}

