/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : FileBrowserTree.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */
package ciss.phase_viewer.filebrowser;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.TreePath;

import ciss.phase_viewer.mainpanel.Desk;
import ciss.phase_viewer.settings.GlobalProperties;
import ciss.phase_viewer.settings.PropertiesManager;
import ciss.phase_viewer.ssh.filechooser.RemoteFile;
import ciss.phase_viewer.ssh.sftp.SftpEvent;
import ciss.phase_viewer.ssh.sftp.SftpListener;

import com.jcraft.jsch.ChannelSftp;

/**
 * リモートホストも含め, ディレクトリーをツリーで表示できるコンポーネント.
 */
public class FileBrowserTree extends JPanel implements SftpListener {
    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger
            .getLogger(FileBrowserTree.class.getName());

    public static final int WINDOWS = 0;
    public static final int UNIX = 1;

    private int type = 0;

    private int os = WINDOWS;
    private String initialDir;
    private String FS = System.getProperty("file.separator");

    private int curdir = 0;
    private RemoteFile[] templist;
    private RemoteFile[][] work = new RemoteFile[512][];
    private RemoteFile root;

    private JScrollPane scrollpane;

    protected JTree tree;

    private Vector listeners = new Vector();

    private boolean isremote = false;
    private ChannelSftp c;

    private boolean initExpand = true;
    private FileBrowserTreeListener parent;

    private String nodeClassString;

    private Vector mouseListeners = new Vector();

    private boolean useCombo = true;

    /**
     * @param os
     *            WINDOWSかUNIXを指定 (リモートの場合のパスセパレーターは決めきれない)
     * @param parent
     *            呼び出し元のコンポーネント; ここで使ういくつかのメソッドが実装ずみなはず.
     * @param nodeClassString
     *            ノードの実装は時と場合に応じて変更される. ここでは, concreteクラスのロードパスを渡す
     * @param init
     *            まず初期化すべきか否かを指定
     */
    public FileBrowserTree(int os, String initialDir,
            FileBrowserTreeListener parent, String nodeClassString, boolean init) {
        super();
        this.os = os;
        this.initialDir = initialDir;
        this.parent = parent;
        this.nodeClassString = nodeClassString;
        if (init) {
            init();
        }
    }

    /**
     * @param os
     *            WINDOWSかUNIXを指定 (リモートの場合のパスセパレーターは決めきれない)
     * @param parent
     *            呼び出し元のコンポーネント; ここで使ういくつかのメソッドが実装ずみなはず.
     * @param nodeClassString
     *            ノードの実装は時と場合に応じて変更される. ここでは, concreteクラスのロードパスを渡す
     * @param init
     *            まず初期化すべきか否かを指定
     * @param useCombo
     *            「上のディレクトリーへ移る」操作を表すGUIがcomboboxならtrue, buttonならfalse.
     */
    public FileBrowserTree(int os, String initialDir,
            FileBrowserTreeListener parent, String nodeClassString,
            boolean init, boolean useCombo) {
        super();
        this.os = os;
        this.initialDir = initialDir;
        this.parent = parent;
        this.nodeClassString = nodeClassString;
        this.useCombo = useCombo;
        if (init) {
            init();
        }
    }

    /**
     * このパネルに関連付けられているJTreeオブジェクトを取得
     * 
     * @return JTreeへの参照
     */
    public JTree getTree() {
        return this.tree;
    }

    public void setChannelSftp(ChannelSftp c) {
        this.c = c;
    }

    public void setParent(FileBrowserTreeListener parent) {
        this.parent = parent;
    }

    public RemoteFile[] getSelectedFiles() {
        TreePath[] paths = tree.getSelectionPaths();
        if (paths == null || paths.length == 0) {
            logger.info("no file selected!");
            return null;
        }

        RemoteFile[] ret = new RemoteFile[paths.length];
        for (int i = 0; i < paths.length; i++) {
            ret[i] = getSubDirList(paths[i]);
        }
        return ret;
    }

    public RemoteFile getCurrentDir() {
        TreePath path = tree.getSelectionPath();
        RemoteFile f = getSubDirList(path);
        if (f == null) {
            return null;
        }
        logger.debug("file: " + f + ", isDir: " + f.isDirectory());
        RemoteFile ret = f;
        if (!f.isDirectory()) {
            ret = (RemoteFile) f.getParentFile();
            logger.debug("dir at getcurrentdir: " + ret);
        }
        return ret;
    }

    public void setRemote(boolean isremote) {
        this.isremote = isremote;
    }

    public boolean isRemote() {
        return isremote;
    }

    public void addFileBrowserListener(FileBrowserTreeListener fbl) {
        listeners.addElement(fbl);
    }

    public void init(String initialDir) {
        this.initialDir = initialDir;
        init();
    }

    /**
     * もともとexpandedだったパスをexpandするようにreload... が, うまく行かない.
     * たぶんtreeWillExpandリスナーによって動的にノードを作っているせいだが, TreePathを順に 手繰る,
     * みたいなメソッドがないようなのでどうしてもできない?
     */
    public void reload() {
        Vector vector = new Vector();
        for (int i = 0; i < tree.getRowCount(); i++) {
            vector.addElement(tree.getExpandedDescendants(tree.getPathForRow(i)));
        }
        // DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)
        // tree.getModel().getRoot();
        // Enumeration enumeration = rootNode.preorderEnumeration();
        // while ( enumeration.hasMoreElements() ) {
        // DefaultMutableTreeNode child = (DefaultMutableTreeNode)
        // enumeration.nextElement();
        // if ( !child.isLeaf() ) {
        // child.
        // }
        // }
        init();
        for (int i = 0; i < vector.size(); i++) {
            Enumeration enumeration = (Enumeration) vector.get(i);
            if (enumeration != null) {
                while (enumeration.hasMoreElements()) {
                    TreePath tp = (TreePath) enumeration.nextElement();
                    logger.debug("expanded path: " + tp.toString());
                    tree.expandPath(tp);
                    tree.scrollPathToVisible(tp);
                }
            }
        }
    }

    public void init() {
        if (os == UNIX) {
            FS = "/";
        }

        String exp = PropertiesManager.getGlobalProperties(
                PropertiesManager.PROPERTIES_PVIEWER).getProperty(
                "file_browser_init_expand");
        if (exp != null && exp.trim().equalsIgnoreCase("false")) {
            initExpand = false;
        }

        this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));

        if (initialDir != null && initialDir.trim().length() != 0
                && os != WINDOWS) {
            logger.debug("initialDir: " + initialDir);
            String[] array = initialDir.trim().split("/");
            String newinitdir = "";
            if (array != null && array.length != 0) {
                for (int i = 0; i < array.length; i++) {
                    if (array[i].trim().length() != 0) {
                        newinitdir += "/" + array[i];
                    }
                }
                initialDir = newinitdir;
                logger.debug("new initdir: " + initialDir);
            }
        }
        if (root == null) {
            if (initialDir == null || !(new RemoteFile(initialDir, c)).exists()) {
                logger.debug("initial dir doesn't exist / null");
                if (os == WINDOWS) {
                    // RemoteFile [] roots = (RemoteFile[])
                    // RemoteFile.listRoots();
                    root = new RemoteFile(System.getProperty("user.home"));
                } else {
                    root = new RemoteFile("/", c);
                }
            } else {
                logger.debug("initial dir exists.");
                root = new RemoteFile(initialDir + FS, c);
            }
        }
        root.isRoot(true);

        createTree();
    }

    /**
     * Rootディレクトリーを新たに指定しなおす. うまく行くかな?
     * 
     * @param newRoot
     *            新しいrootへのパス
     */
    public void setRoot(String newRoot) {
        RemoteFile test = new RemoteFile(newRoot, c);
        if (!test.exists()) {
            return;
        }
        initialDir = newRoot;
        root = new RemoteFile(newRoot, c);
        root.isRoot(true);
        init();
    }

    /** ファイル操作関連のメソッド群; should be obvious */
    private GlobalProperties gp = PropertiesManager
            .getGlobalProperties(PropertiesManager.PROPERTIES_PVIEWER);

    public void bootFilePropertyViewer() {
        RemoteFile[] selectedFiles = getSelectedFiles();
        if (selectedFiles != null) {
            FilePropertyViewer viewer = new FilePropertyViewer(selectedFiles);
        }
    }

    public void createNewFile() {
        RemoteFile file = getSelectedFiles()[0];
        String message = "enter new file name";
        RemoteFile newfile = getNewFile(message, file);
        if (newfile == null) {
            logger.info("canceled.");
            return;
        }
        try {
            if (!newfile.createNewFile()) {
                logger.error("failed to create file " + file);
            }
        } catch (IOException ioe) {
        }
        init();
    }

    private boolean bootingEditor = false;
    private boolean editor = true;
    private String[] tmpFiles;

    /**
     * 可能な場合はeditorよりも相応しいプログラムで選択中のファイルを眺める.
     * 
     * @param editor
     *            いつ何時でもエディターを利用してbootする場合はtrue.
     */
    public void bootProgram(boolean editor) {
        this.editor = editor;
        RemoteFile[] selectedFiles = getSelectedFiles();
        if (selectedFiles != null && !isRemote()) {
            for (int i = 0; i < selectedFiles.length; i++) {
                FileBrowserUtils.bootProgram(editor,
                        selectedFiles[i].getAbsolutePath());
            }
        } else if (selectedFiles != null && isRemote()) {
            Vector tmpvec = new Vector();
            for (int i = 0; i < selectedFiles.length; i++) {
                if (!selectedFiles[i].isDirectory()) {
                    tmpvec.addElement(selectedFiles[i].getName());
                }
            }
            if (tmpvec.size() == 0) {
                return;
            }
            bootingEditor = true;
            tmpFiles = new String[tmpvec.size()];
            tmpvec.copyInto(tmpFiles);
            transfer.downloadToTmpFile();
        }
    }

    public void deleteSelectedFiles() {
        boolean warn = true;
        String ask = gp.getProperty("sftp_client_ask_delfile");
        if (ask != null) {
            logger.debug("ask: " + ask);
            if (ask.trim().equals("false")) {
                warn = false;
                logger.debug("deleteing file without warning");
            } else {
                logger.debug("warn");
            }
        } else {
            logger.debug("sftp_client_ask_deflfile not set, using default");
        }

        RemoteFile[] selectedFiles = getSelectedFiles();
        if (selectedFiles != null) {
            int res = JOptionPane.OK_OPTION;
            if (warn) {
                String message = "deleting " + selectedFiles.length
                        + " file(s) ... proceed?";
                res = JOptionPane.showInternalConfirmDialog(Desk.getDesktop(),
                        message);
            }

            if (res == JOptionPane.OK_OPTION) {
                for (int i = 0; i < selectedFiles.length; i++) {
                    logger.info("deleteing file: "
                            + selectedFiles[i].getAbsolutePath());
                    boolean b = selectedFiles[i].delete();
                    if (!b) {
                        logger.warn("failed to delete: "
                                + selectedFiles[i].getAbsolutePath());
                    }
                }
            }
        }
        init();
    }

    public void mkdir() {
        logger.debug("running....");
        RemoteFile file = getSelectedFiles()[0];
        String message = "enter new directory name";
        RemoteFile newdir = getNewFile(message, file);
        if (newdir == null) {
            logger.info("canceled.");
            return;
        }
        new Thread(new DirMaker(newdir)).start();
    }

    class DirMaker implements Runnable {
        private RemoteFile newdir;

        protected DirMaker(RemoteFile newdir) {
            this.newdir = newdir;
            logger.debug("instantiated DirMaker");
        }

        public void run() {
            logger.info("creating new dir: " + newdir.getAbsolutePath());
            newdir.mkdir();
            init();
        }
    }

    public void rename() {
        RemoteFile file = getSelectedFiles()[0];
        String message = "enter new file name";
        RemoteFile renameTo = getNewFile(message, file);
        if (renameTo == null) {
            logger.info("canceled.");
            return;
        }
        new Thread(new Renamer(file, renameTo)).start();
    }

    class Renamer implements Runnable {
        private RemoteFile origfile;
        private RemoteFile newfile;

        protected Renamer(RemoteFile origfile, RemoteFile newfile) {
            this.origfile = origfile;
            this.newfile = newfile;
        }

        public void run() {
            boolean res = origfile.renameTo(newfile);
            if (!res) {
                logger.warn("failed rename");
                return;
            }
            init();
        }
    }

    private FileTransfer transfer;

    /**
     * ファイル転送を担当するクラスをセット.
     * 
     * @param transfer
     *            ファイル転送を行うことができるオブジェクト.
     */
    public void setFileTransfer(FileTransfer transfer) {
        this.transfer = transfer;
    }

    /**
     * ファイルをアップロード; 実際どんな具合にアップロードするかは登録されている FileTransferの実装に依存する.
     */
    public void uploadFiles() {
        if (transfer != null) {
            transfer.uploadFiles();
        }
    }

    /**
     * ファイルをダウンロード; 実際どんな具合にダウンロードするかは登録されている FileTransferの実装に依存する.
     */
    public void downloadFiles() {
        if (transfer != null) {
            transfer.downloadFiles();
        }
    }

    /**
     * 「選択中のファイルのパス」を取得
     * 
     * @return 選択中のファイルの配列
     */
    public String[] getSelectedPaths() {
        RemoteFile[] files = getSelectedFiles();
        if (files == null || files.length == 0) {
            return null;
        }
        String[] ret = new String[files.length];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = files[i].getAbsolutePath();
        }
        return ret;
    }

    /**
     * 現在「接続中」か否かを返す. FileTransferが登録されていない場合, 必ずfalse.
     */
    public boolean isConnected() {
        if (transfer != null) {
            return transfer.isConnected();
        }
        return false;
    }

    private RemoteFile getNewFile(String message, RemoteFile file) {
        logger.debug("getting file name....");
        String name = JOptionPane.showInternalInputDialog(Desk.getDesktop(),
                message);
        logger.debug("got file name: " + name);
        if (name == null) {
            return null;
        }

        RemoteFile parent = null;
        if (!file.isDirectory()) {
            parent = (RemoteFile) file.getParentFile();
        } else {
            parent = new RemoteFile(file.getAbsolutePath(), c);
        }

        String newfile = "";
        String fs = file.getSeparator();
        if (parent != null) {
            newfile = parent.getAbsolutePath() + fs;
            newfile += name;
        }

        return new RemoteFile(newfile, c);
    }

    private void createTree() {
        FileBrowserNode treenode = FileBrowserNode.getInstance(nodeClassString,
                new Object[] { root });

        tree = new JTree(treenode);
        tree.setSelectionRow(0);

        // treenode.add(new DefaultMutableTreeNode(""));
        FileBrowserNode treenode_tmp = FileBrowserNode.getInstance(
                nodeClassString, new Object[] { "" });
        treenode.add(treenode_tmp);

        tree.addTreeWillExpandListener(new TreeWillExpandListener() {
            // ツリーが展開されようとしている
            public void treeWillExpand(TreeExpansionEvent e) {
                // 現在のツリーパスを取得する
                TreePath tp = e.getPath();
                // パス文字列を生成する
                RemoteFile file = getSubDirList(tp);
                // リストを更新して、展開用のツリーを作成する
                addExpandTree(tp, file);
            }

            // ツリーが折り畳まれようとしている
            public void treeWillCollapse(TreeExpansionEvent e) {
                // 現在のツリーパスを取得する
                TreePath tp = e.getPath().getParentPath();
                // パス文字列を生成する
                RemoteFile file = getSubDirList(tp);
                // 折り畳んだパスのリストを表示する
                showDirectory(tp, file);
            }
        });

        tree.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent e) {
                notifyListeners();
            }
        });

        tree.addMouseListener(new MouseListenerTree(this));
        tree.addKeyListener(new KeyListenerTree(this));
        removeAll();

        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));

        if (useCombo) {
            ComboBoxFileBrowser combo = new ComboBoxFileBrowser(this, c);
            p.add(combo);
        } else {
            BtnUp up = new BtnUp(this, c);
            p.add(up);
        }

        JButton btnreload = new JButton("reload");
        p.add(btnreload);

        this.add(p);
        scrollpane = new JScrollPane(tree);
        this.add(scrollpane);

        if (initExpand) {
            tree.expandRow(0);
        }

        this.revalidate();

        btnreload.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                reload();
            }
        });
    }

    private void notifyListeners() {
        FileBrowserEvent fbe = new FileBrowserEvent(this);
        for (int i = 0; i < listeners.size(); i++) {
            ((FileBrowserTreeListener) listeners.elementAt(i))
                    .directoryHasChanged(fbe);
        }
    }

    private void addExpandTree(TreePath tp, RemoteFile file) {
        // リストを更新する
        if (file == null) {
            return;
        }
        FileBrowserNode node = (FileBrowserNode) tp.getLastPathComponent();
        logger.debug("at addExpandTree: " + node.toString());
        if (node == null) {
            return;
        }

        // ディレクトリの場合は、リストも更新する
        if (file.isDirectory()) {
            // リストを更新する
            // /////////////////////////////////////////
            // サブディレクトリをリストする
            templist = (RemoteFile[]) file.listFiles();
            // リストが空の場合は処理しない
            if (templist.length == 0)
                return;

            Arrays.sort(templist);
            Vector dirs = new Vector();
            Vector fil = new Vector();
            for (int i = 0; i < templist.length; i++)
                if (templist[i].isDirectory())
                    dirs.add(templist[i]);
                else
                    fil.add(templist[i]);
            int count = 0;
            for (int i = 0; i < dirs.size(); i++) {
                templist[count] = (RemoteFile) dirs.get(i);
                count++;
            }
            for (int i = 0; i < fil.size(); i++) {
                templist[count] = (RemoteFile) fil.get(i);
                count++;
            }

            curdir++;
            work[curdir] = templist;
            logger.debug("templist");
            for (int i = 0; i < templist.length; i++) {
                logger.debug("templist: " + templist[i].getAbsolutePath());
            }

            // Arrays.sort(work[curdir]);
            // /////////////////////////////////////////

            // 次に、ツリーを更新する
            // このノードの現在のすべての子ノードを除去する
            node.removeAllChildren();

            // 2レベルの子ノードを追加する
            for (int i = 0; i < work[curdir].length; i++) {
                FileBrowserNode temp = FileBrowserNode.getInstance(
                        nodeClassString, new Object[] { work[curdir][i] });
                logger.debug("at addExpandTree: " + temp.toString());
                node.add(temp);
                if (work[curdir][i].isDirectory()) {
                    // このディレクトリのリストを作成する
                    RemoteFile[] files = (RemoteFile[]) work[curdir][i]
                            .listFiles();
                    // リストが空の場合は処理しない
                    if (files == null || files.length == 0)
                        continue;
                    // 昇順にソートする
                    Arrays.sort(files);

                    // リストの各ノードをツリーに追加する
                    for (int j = 0; j < files.length; j++) {
                        // FileBrowserNode tempsub =
                        // FileBrowserNode.getInstance(files[j].getName(),type);
                        FileBrowserNode tempsub = FileBrowserNode.getInstance(
                                nodeClassString, new Object[] { files[j] });
                        // このノードを追加する
                        logger.debug("tempsub: " + tempsub.toString());
                        temp.add(tempsub);
                    }
                }
            }
        }
    }

    private void showDirectory(TreePath tp, RemoteFile file) {
        // リストを更新する
        if (file == null) {
            templist = new RemoteFile[1];
            curdir++;
            work[curdir] = templist;
            return;
        }
        // // DefaultMutableTreeNode node = (DefaultMutableTreeNode)
        // tp.getLastPathComponent();
        // SftpNode node = (SftpNode) tp.getLastPathComponent();
        FileBrowserNode node = (FileBrowserNode) tp.getLastPathComponent();
        if (node == null) {
            return;
        }

        // ディレクトリの場合は、リストを更新する
        if (file.isDirectory()) {
            // サブディレクトリをリストする
            templist = (RemoteFile[]) file.listFiles();
            // リストが空の場合は処理しない
            if (templist.length == 0)
                return;
            curdir++;

            Vector dirs = new Vector();
            Vector files = new Vector();
            for (int i = 0; i < templist.length; i++)
                if (templist[i].isDirectory())
                    dirs.add(templist[i]);
                else
                    files.add(templist[i]);
            int count = 0;
            for (int i = 0; i < dirs.size(); i++) {
                templist[count] = (RemoteFile) dirs.get(i);
                count++;
            }
            for (int i = 0; i < files.size(); i++) {
                templist[count] = (RemoteFile) files.get(i);
                count++;
            }

            work[curdir] = templist;
            // Arrays.sort(work[curdir]);
            // list.setListData(work[curdir]);
        }
    }

    private RemoteFile getSubDirList(TreePath treepath) {
        if (treepath == null) {
            return null;
        }
        // まず、ルートディレクトリを取得する

        String string = treepath.getPathComponent(0).toString();
        if (!string.trim().endsWith(FS)) {
            string += FS;
        }
        // 次にパスカウントを取得する
        int count = treepath.getPathCount();
        // その後、残りのパス文字列を取得する
        for (int i = 1; i < count; i++) {
            string += treepath.getPathComponent(i).toString();
            if (i + 1 == count) {
                break;
            } else {
                string += FS;
            }
        }
        logger.debug("subdirlist: " + string);
        return new RemoteFile(string, c);
    }

    protected RemoteFile getCurrentRoot() {
        return root;
    }

    protected void setNewRoot(RemoteFile newRoot) {
        this.root = newRoot;
        root.isRoot(true);
        createTree();
    }

    protected ChannelSftp getChannelSftp() {
        return this.c;
    }

    public void uploadFinished(SftpEvent e) {
    }

    public void downloadFinished(SftpEvent e) {
        if (bootingEditor && tmpFiles != null && tmpFiles.length != 0) {
            String dir = System.getProperty("java.io.tmpdir")
                    + System.getProperty("file.separator");
            for (int i = 0; i < tmpFiles.length; i++) {
                FileBrowserUtils.bootProgram(editor, dir + tmpFiles[i]);
            }
            bootingEditor = false;
        }
    }

}

/**
 * ComboBoxだとパス名が長すぎてみっともない時もある. これは, 単に「上のディレクトリーへ」みたいなボタンの役割を果たす
 */
class BtnUp extends JButton {
    private RemoteFile root;
    private FileBrowserTree tree;
    private ChannelSftp c;

    BtnUp(FileBrowserTree tree, ChannelSftp c) {
        super("go up");
        this.tree = tree;
        this.c = c;
        init();
    }

    private void init() {
        addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                RemoteFile cur = tree.getCurrentRoot();
                String parentdir = cur.getParent();
                RemoteFile newRoot = new RemoteFile(parentdir, c);
                newRoot.isRoot(true);
                tree.setNewRoot(newRoot);
            }
        });
    }
}

class ComboBoxFileBrowser extends JComboBox {
    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger
            .getLogger(ComboBoxFileBrowser.class.getName());
    private RemoteFile root;
    private FileBrowserTree tree;

    private ChannelSftp c;

    protected ComboBoxFileBrowser(FileBrowserTree tree, ChannelSftp c) {
        super();
        this.tree = tree;
        this.c = c;
        createCombo();
    }

    private void addListener() {
        addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                RemoteFile newRoot = new RemoteFile((String) getSelectedItem(),
                        c);
                newRoot.isRoot(true);
                tree.setNewRoot(newRoot);
                createCombo();
            }
        });
    }

    private void createCombo() {
        removeAllItems();
        root = tree.getCurrentRoot();
        if (root != null) {
            RemoteFile[] parents = getParents(root);
            String[] itemStrings = null;

            if (parents != null && parents.length != 0) {
                itemStrings = new String[parents.length];
                for (int i = 0; i < parents.length; i++) {
                    logger.debug("parent no. " + i + " is " + parents[i]);
                    itemStrings[i] = parents[i].getAbsolutePath();
                }

                for (int i = parents.length - 1; i >= 0; i--) {
                    logger.debug("adding item no. " + i + ", elem "
                            + itemStrings[i]);
                    addItem(itemStrings[i]);
                }
            }

            setSelectedIndex(getItemCount() - 1);
            addListener();
            setVisible(false);
            setVisible(true);
            revalidate();
        }
    }

    private RemoteFile[] getParents(RemoteFile base) {
        String dirnow = new String(base.getAbsolutePath());
        RemoteFile parent = base;
        Vector fileVector = new Vector();
        int parentCount = 0;
        fileVector.addElement(parent);
        while (true) {
            parentCount++;
            parent = (RemoteFile) parent.getParentFile();
            if (parentCount >= 100 || parent == null) {
                break;
            }
            logger.debug("parentdir: " + parent);
            fileVector.addElement(parent);
        }
        logger.debug("num. parents: " + parentCount);

        // if ( dirnow != null && c != null ) {
        // logger.debug("dirnow: "+dirnow);
        // try {
        // c.cd(dirnow);
        // logger.debug("restored directory to: "+c.pwd());
        // } catch(SftpException se) {
        // logger.error("failed to restore directory");
        // } catch(NullPointerException npe) {
        // logger.error("null found");
        // npe.printStackTrace();
        // }
        // }

        RemoteFile[] retFile = new RemoteFile[fileVector.size()];
        for (int i = 0; i < fileVector.size(); i++) {
            retFile[i] = (RemoteFile) fileVector.get(i);
        }

        return retFile;
    }

}

/**
 * ノードは, ツーリーノード選択時に動的に作成される. そうでないとファイルがたくさんある場合かなり おそくなる. これはそのためのリスナー.
 */
class MouseListenerTree extends MouseAdapter {
    private static org.apache.log4j.Logger logger = org.apache.log4j.Logger
            .getLogger(MouseListenerTree.class.getName());
    private FileBrowserTree parent;

    protected MouseListenerTree(FileBrowserTree parent) {
        this.parent = parent;
    }

    public void mousePressed(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
        JTree tree = (JTree) e.getSource();
        TreePath path = tree.getPathForLocation(x, y);
        if (path != null) {
            // DefaultMutableTreeNode node = (DefaultMutableTreeNode)
            // path.getLastPathComponent();
            FileBrowserNode node = (FileBrowserNode) path
                    .getLastPathComponent();
            node.mousePressed(e, parent);
            logger.debug("selected node: " + node);
        }
        int row = tree.getRowForLocation(x, y);
        logger.debug("selected row: " + row);
    }

    public void mouseClicked(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
        JTree tree = (JTree) e.getSource();
        TreePath path = tree.getPathForLocation(x, y);
        if (path != null) {
            // DefaultMutableTreeNode node = (DefaultMutableTreeNode)
            // path.getLastPathComponent();
            FileBrowserNode node = (FileBrowserNode) path
                    .getLastPathComponent();
            node.mouseClicked(e, parent);
            logger.debug("selected node: " + node);
        }
    }

}

class KeyListenerTree extends KeyAdapter {
    private FileBrowserTree parent;

    protected KeyListenerTree(FileBrowserTree parent) {
        this.parent = parent;
    }

    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
            parent.bootProgram(false);
        }
    }

    public void keyTyped(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
            parent.bootProgram(false);
        }
    }

}
