org.cfeclipse.cfml.views.explorer.vfs.view.VFSView.java Source code

Java tutorial

Introduction

Here is the source code for org.cfeclipse.cfml.views.explorer.vfs.view.VFSView.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.cfeclipse.cfml.views.explorer.vfs.view;

import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.preference.PreferenceDialog;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.swt.*;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.program.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PreferencesUtil;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.part.ViewPart;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.text.*;
import java.util.*;

import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileSystemManager;
import org.apache.commons.vfs.FileSystemOptions;
import org.apache.commons.vfs.VFS;
import org.apache.commons.vfs.auth.StaticUserAuthenticator;
import org.apache.commons.vfs.impl.DefaultFileSystemConfigBuilder;
import org.apache.commons.vfs.provider.ftp.FtpFileSystemConfigBuilder;
import org.apache.commons.vfs.provider.sftp.SftpFileSystemConfigBuilder;
import org.apache.commons.vfs.util.Os;

import org.cfeclipse.cfml.editors.CFJavaFileEditorInput;
import org.cfeclipse.cfml.editors.CFMLEditor;
import org.cfeclipse.cfml.images.ColumnViewerToolTipSupport;
import org.cfeclipse.cfml.images.ToolTip;
import org.cfeclipse.cfml.net.FTPConnectionProperties;
import org.cfeclipse.cfml.net.RemoteFile;
import org.cfeclipse.cfml.net.RemoteFileEditorInput;
import org.cfeclipse.cfml.preferences.FtpConnectionDialog;
import org.cfeclipse.cfml.util.CFPluginImages;
import org.cfeclipse.cfml.views.explorer.FileExplorerView;
import org.cfeclipse.cfml.views.explorer.vfs.FileOperation;
import org.cfeclipse.cfml.views.explorer.vfs.view.menus.EditMenu;
import org.cfeclipse.cfml.views.explorer.vfs.view.menus.FileMenu;
import org.cfeclipse.cfml.views.explorer.vfs.view.menus.EditMenu.CopyAction;
import org.cfeclipse.cfml.views.explorer.vfs.view.menus.EditMenu.PasteAction;
import org.cfeclipse.cfml.views.explorer.vfs.view.menus.FileMenu.ConnectionAction;
import org.cfeclipse.cfml.views.explorer.vfs.view.menus.FileMenu.DeleteAction;
import org.cfeclipse.cfml.views.explorer.vfs.view.menus.FileMenu.NewFileAction;
import org.cfeclipse.cfml.views.explorer.vfs.view.menus.FileMenu.NewFolderAction;

/**
 * File Viewer example
 */
public class VFSView extends ViewPart implements IShowInTarget {
    public static final String ID_FILEEXPLORER = "org.cfeclipse.cfml.views.explorer.vfs.view.VFSView";
    private static final String RESOURCE_BUNDLE = VFSView.class.getName();
    private static ResourceBundle resourceBundle = ResourceBundle.getBundle(RESOURCE_BUNDLE);

    private final static String DRIVE_A = "a:" + FileName.SEPARATOR;
    //   private final static String DRIVE_B = "b:" + FileName.SEPARATOR;
    private static boolean initial = true;

    /* UI elements */
    private Display display;
    private Shell shell;
    //   private ToolBar toolBar;

    private Label numObjectsLabel;
    private Label diskSpaceLabel;

    /* currently selected directory */
    private FileObject currentDirectory = null;

    /* Drag and drop optimizations */
    private boolean isDragging = false; // if this app is dragging
    private boolean isDropping = false; // if this app is dropping

    //   private FileObject[]  processedDropFiles = null; // so Drag only deletes what it needs to
    private FileObject[] deferredRefreshFiles = null; // to defer notifyRefreshFiles while we do DND

    private boolean deferredRefreshRequested = false; // to defer notifyRefreshFiles while we do DND

    /* Combo view */
    private static final String COMBODATA_ROOTS = "Combo.roots";
    // File[]: Array of files whose paths are currently displayed in the combo
    private static final String COMBODATA_LASTTEXT = "Combo.lastText";
    // String: Previous selection text string
    private static final String COMBODATA_LASTCONNECTIONID = "Combo.lastConnectionId";
    // String: Previous connectionid

    private Combo combo;

    /* Tree view */
    static public ImageRegistry iconCache = CFPluginImages.getImageRegistry();

    public static final String TREEITEMDATA_FILE = "TreeItem.file";
    // File: File associated with tree item
    public static final String TREEITEMDATA_IMAGEEXPANDED = "TreeItem.imageExpanded";
    // Image: shown when item is expanded
    public static final String TREEITEMDATA_IMAGECOLLAPSED = "TreeItem.imageCollapsed";
    // Image: shown when item is collapsed
    private static final String TREEITEMDATA_STUB = "TreeItem.stub";
    // Object: if not present or null then the item has not been populated

    public static final String TREEITEMDATA_URI = "TreeItem.uri";
    // VFS URI associated with table row
    public static final String TREEITEMDATA_CONNECTIONID = "TableItem.connectionId";
    // connection id 

    private static Tree tree;
    private Label treeScopeLabel;

    /* Table view */
    protected static final DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM,
            DateFormat.MEDIUM);

    public static final String TABLEITEMDATA_FILE = "TableItem.file";
    // File: File associated with table row
    public static final String TABLEDATA_DIR = "Table.dir";
    // File: Currently visible directory

    private static final int[] tableWidths = new int[] { 150, 60, 75, 150 };
    public static final String TABLEITEMDATA_CONNECTIONID = "Table.connectionId";
    protected static final String TABLEDATA_CONNECTIONID = "TableItem.connectionId";
    private final String[] tableTitles = new String[] { VFSView.getResourceString("table.Name.title"),
            VFSView.getResourceString("table.Size.title"), VFSView.getResourceString("table.Type.title"),
            VFSView.getResourceString("table.Modified.title") };
    private Table table;

    /* File Items Tabs */
    private TabItem tabFileItems;
    //   private TabItem tabTrasnferItems;

    /* Table update worker */
    // Control data
    private final Object workerLock = new Object();
    // Lock for all worker control data and state
    private volatile Thread workerThread = null;
    // The worker's thread
    private volatile boolean workerStopped = false;
    // True if the worker must exit on completion of the current cycle
    private volatile boolean workerCancelled = false;
    // True if the worker must cancel its operations prematurely perhaps due to a state update

    // Worker state information -- this is what gets synchronized by an update
    private volatile FileObject workerStateDir = null;

    // State information to use for the next cycle
    private volatile FileObject workerNextDir = null;

    // Comnnons VFS Manager
    private FileSystemManager fsManager = null;
    public TreeMap<String, FTPConnectionProperties> fConnections;
    private String currentConnectionId;
    private String workerNextConnectionId;
    protected String workerStateConnectionId;
    public Object fSourceConnectionId;
    private ConnectionAction connectionAction;
    private DeleteAction deleteAction;
    private CopyAction copyAction;
    private PasteAction pasteAction;
    private NewFolderAction newFolderAction;
    private NewFileAction newFileAction;
    //private static boolean VFS_DEBUG = System.getProperty("org.cfeclipse.cfml.views.explorer.vfs.debug") != null;
    private static boolean VFS_DEBUG = true;

    public static void error(String s) {
        if (VFS_DEBUG)
            System.err.println(s);
    }

    public static void debug(Exception e) {
        if (VFS_DEBUG)
            e.printStackTrace();
    }

    public Tree getTree() {
        return tree;
    }

    public Table getTable() {
        return table;
    }

    /**
     * Returns the Commons VFS File System Manager
     */
    public FileSystemManager getFileSystemManager() {
        return fsManager;
    }

    /**
     * Closes the main program.
     */
    public void dispose() {
        workerStop();
        //return super.close();
    }

    /**
     * Returns a string from the resource bundle.
     * We don't want to crash because of a missing String.
     * Returns the key if not found.
     */
    public static String getResourceString(String key) {
        try {
            return resourceBundle.getString(key);
        } catch (MissingResourceException e) {
            return key;
        } catch (NullPointerException e) {
            return "!" + key + "!";
        }
    }

    /**
     * Returns a string from the resource bundle and binds it
     * with the given arguments. If the key is not found,
     * return the key.
     */
    public static String getResourceString(String key, Object[] args) {
        try {
            return MessageFormat.format(getResourceString(key), args);
        } catch (MissingResourceException e) {
            return key;
        } catch (NullPointerException e) {
            return "!" + key + "!";
        }
    }

    /**
     * Returns a string from the resource bundle and binds it
     * with the given arguments. If the key is not found,
     * return the key.
     */
    public static String getResourceString(String key, Object arg) {
        try {
            return MessageFormat.format(getResourceString(key), new Object[] { arg });
        } catch (MissingResourceException e) {
            return key;
        } catch (NullPointerException e) {
            return "!" + key + "!";
        }
    }

    /**
     * Returns a new menu manager for the window
     */
    protected MenuManager createMenuManager() {
        MenuManager menuManager = (MenuManager) getViewSite().getActionBars().getMenuManager();
        // File
        MenuManager fileMenu = new MenuManager(getResourceString("menu.File.text"));
        connectionAction = new FileMenu.ConnectionAction(this, getResourceString("menu.File.New.text"),
                getResourceString("menu.File.New.text"));
        fileMenu.add(connectionAction);

        deleteAction = new FileMenu.DeleteAction(this, getResourceString("menu.Connection.Delete.text"),
                getResourceString("menu.Connection.Delete.text"));
        fileMenu.add(deleteAction);

        newFolderAction = new FileMenu.NewFolderAction(this, getResourceString("menu.File.NewFolder.text"),
                getResourceString("menu.File.NewFolder.text"));
        fileMenu.add(newFolderAction);

        newFileAction = new FileMenu.NewFileAction(this, getResourceString("menu.File.NewFile.text"),
                getResourceString("menu.File.NewFile.text"));
        fileMenu.add(newFileAction);

        // Edit
        MenuManager editMenu = new MenuManager(getResourceString("menu.Edit.text"));
        copyAction = new EditMenu.CopyAction(this, getResourceString("menu.Edit.copy.text"),
                getResourceString("menu.Edit.copy.text"));
        editMenu.add(copyAction);

        pasteAction = new EditMenu.PasteAction(this, getResourceString("menu.Edit.paste.text"),
                getResourceString("menu.Edit.paste.text"));
        editMenu.add(pasteAction);

        menuManager.add(fileMenu);
        menuManager.add(editMenu);

        // hook the tree context menu
        MenuManager menuMgr = new MenuManager("#PopupMenu");
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                VFSView.this.fillContextMenu(manager, new Action[] { connectionAction, deleteAction, copyAction,
                        pasteAction, newFolderAction, newFileAction });
            }
        });
        Menu menu = menuMgr.createContextMenu(tree);
        tree.setMenu(menu);

        // hook the table context menu
        MenuManager tabMenuMgr = new MenuManager("#PopupMenu");
        tabMenuMgr.setRemoveAllWhenShown(true);
        tabMenuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                VFSView.this.fillContextMenu(manager,
                        new Action[] { copyAction, pasteAction, newFolderAction, newFileAction });
            }
        });
        Menu tabMenu = tabMenuMgr.createContextMenu(table);
        table.setMenu(tabMenu);

        return menuManager;
    }

    private void fillContextMenu(IMenuManager manager, Action[] actions) {
        //Somehow we have to know what this item is
        for (Action action : actions) {
            manager.add(action);
        }
    }

    /**
      * Helper method to calculate the size of the cool item
      * 
      * @param item the cool item
      */
    private void calcSize(CoolItem item) {
        Control control = item.getControl();
        Point pt = control.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        pt = item.computeSize(pt.x, pt.y);
        item.setSize(pt);
    }

    /**
     * Construct the UI
     * 
     * @param container the ShellContainer managing the Shell we are rendering inside
     */
    public void createPartControl(Composite parent) {
        display = parent.getDisplay();
        shell = getSite().getShell();
        fConnections = VFSUtil.loadFileSystems();
        try {
            fsManager = VFS.getManager();
        } catch (FileSystemException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        Composite container = new Composite(parent, SWT.NONE);
        container.setLayout(new GridLayout(1, true));

        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        //      gridData.horizontalSpan = 3;
        gridData.grabExcessHorizontalSpace = true;
        //      createComboView(parent, gridData); // shell, gridData);

        CoolBar coolbar = new CoolBar(container, SWT.NONE);
        coolbar.setLayoutData(gridData);

        // Create a toolbar coolitem
        final CoolItem item1 = new CoolItem(coolbar, SWT.NONE);
        item1.setControl(createToolBar(coolbar));
        calcSize(item1);

        // Create a combo coolitem
        gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.widthHint = 185;

        final CoolItem item2 = new CoolItem(coolbar, SWT.NONE);
        item2.setControl(createComboView(coolbar, gridData));
        calcSize(item2);

        SashForm sashForm = new SashForm(container, SWT.NONE);
        sashForm.setOrientation(SWT.VERTICAL);

        gridData = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
        sashForm.setLayoutData(gridData);

        createTreeView(sashForm);
        createTableView(sashForm);

        sashForm.setWeights(new int[] { 2, 5 });

        Composite container1 = new Composite(container, SWT.NONE);
        container1.setLayout(new GridLayout(2, true));
        container1.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));

        numObjectsLabel = new Label(container1, SWT.BORDER);
        numObjectsLabel.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true));// gridData);

        diskSpaceLabel = new Label(container1, SWT.BORDER);
        diskSpaceLabel.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true)); //gridData);
        createMenuManager();
        try {
            notifyRefreshFiles(null);
        } catch (Exception e) {
            debug(e);
        }
        //return container; 
    }

    /**
     * Creates the toolbar
     * 
     * @param shell the shell on which to attach the toolbar
     * @param layoutData the layout data
     */
    private Control createToolBar(final Composite shell) { //, Object layoutData) {
        final ToolBar toolBar = new ToolBar(shell, SWT.FLAT);
        //      toolBar.setLayoutData(layoutData);

        // New VFS
        ToolItem item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.get(CFPluginImages.ICON_DRIVE_FTP));//.iconFile]);
        //item.setText(getResourceString("tool.New.vfs.text"));
        item.setToolTipText(getResourceString("tool.New.vfs.tip"));

        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                connectionAction.run();
            }
        });

        // Home
        item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.get(CFPluginImages.ICON_HOME));
        //item.setText(getResourceString("tool.home.text"));
        item.setToolTipText(getResourceString("tool.home.tip"));

        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                try {
                    // Jump the file system home directory, some FS don't have homes

                    String homeDir = getCurrentDirectory().getName().getScheme().equals("file") ? VFSUtil.USER_HOME
                            : (String) getCurrentDirectory().getFileSystem().getAttribute("HOME_DIRECTORY");

                    // Build a URI from the home dir
                    String uri = Os.isFamily(Os.OS_FAMILY_UNIX)
                            ? getCurrentDirectory().getName().getRootURI() + homeDir
                            : getCurrentDirectory().getName().getScheme() + ":///" + homeDir.replace('\\', '/');

                    FileObject fo = resolveURI(uri, getCurrentConnectionId());

                    if (fo.exists())
                        notifySelectedDirectory(fo, getCurrentConnectionId());
                    else
                        VFSUtil.MessageBoxInfo(getResourceString("error.jump.home", new Object[] { homeDir }));

                } catch (FileSystemException ex) {
                    FileObject fo;
                    try {
                        fo = resolveURI(fConnections.get(getCurrentConnectionId()).getURI().toString(),
                                getCurrentConnectionId());
                        if (fo.exists())
                            notifySelectedDirectory(fo, getCurrentConnectionId());
                        else
                            VFSUtil.MessageBoxInfo(
                                    getResourceString("error.jump.home", new Object[] { fo.getURL().toString() }));
                    } catch (FileSystemException e1) {
                        VFSUtil.MessageBoxInfo(ex.getMessage());
                    }
                }
            }
        });

        // New folder
        item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.get(CFPluginImages.ICON_FOLDER_NEW));
        //item.setText(getResourceString("tool.New.folder.text"));
        item.setToolTipText(getResourceString("tool.New.folder.tip"));

        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                VFSUtil.newFolder(getCurrentDirectory());
            }
        });

        item = new ToolItem(toolBar, SWT.SEPARATOR);

        // Parent
        item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.get(CFPluginImages.ICON_FOLDER_PARENT));
        //item.setText(getResourceString("tool.Parent.tiptext"));
        item.setToolTipText(getResourceString("tool.Parent.tiptext"));
        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                doParent();
            }
        });

        // Refresh
        item = new ToolItem(toolBar, SWT.PUSH);
        item.setImage(iconCache.get(CFPluginImages.ICON_REFRESH));
        //item.setText(getResourceString("tool.Refresh.tiptext"));
        item.setToolTipText(getResourceString("tool.Refresh.tiptext"));
        item.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                doRefresh();
            }
        });

        return toolBar;
    }

    /**
     * Creates the combo box view.
     * 
     * @param parent the parent control
     */
    private Control createComboView(Composite parent, Object layoutData) {
        combo = new Combo(parent, SWT.NONE);
        combo.setLayoutData(layoutData);
        combo.addSelectionListener(new SelectionAdapter() {

            public void widgetSelected(SelectionEvent e) {
                final FileObject[] roots = (FileObject[]) combo.getData(COMBODATA_ROOTS);
                if (roots == null)
                    return;
                int selection = combo.getSelectionIndex();

                if (selection >= 0 && selection < roots.length) {
                    try {
                        System.out.println("combo.widgetSelected " + roots[selection]);
                        notifySelectedDirectory(roots[selection], roots[selection].getName().toString());
                    } catch (Exception ex) {
                        System.err.println("createComboView.widgetSelected: " + ex);
                    }
                }
            }

            public void widgetDefaultSelected(SelectionEvent e) {
                final String lastText = (String) combo.getData(COMBODATA_LASTTEXT);
                final String lastConnectionId = (String) combo.getData(COMBODATA_LASTCONNECTIONID);
                String text = combo.getText();
                if (text == null)
                    return;
                if (lastText != null && lastText.equals(text))
                    return;
                combo.setData(COMBODATA_LASTTEXT, text);

                try {
                    FileObject file = resolveURI(text, lastConnectionId);

                    if (file.exists())
                        notifySelectedDirectory(file, lastConnectionId);
                    else {
                        VFSUtil.MessageBoxInfo(getResourceString("file.not.found", new Object[] { file }));
                        combo.setText(getCurrentDirectory().toString());
                    }

                } catch (FileSystemException ex) {
                    System.err.println("createComboView::widgetDefaultSelected Error: " + ex);
                }
            }
        });
        return combo;
    }

    /**
     * Creates the file tree view.
     * 
     * @param parent the parent control
     */
    private void createTreeView(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 1;
        gridLayout.marginHeight = gridLayout.marginWidth = 2;
        gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0;
        composite.setLayout(gridLayout);

        treeScopeLabel = new Label(composite, SWT.BORDER);
        treeScopeLabel.setText(VFSView.getResourceString("details.AllFolders.text"));
        treeScopeLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));

        tree = new Tree(composite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE);
        tree.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));

        tree.addSelectionListener(new SelectionListener() {
            public void widgetSelected(SelectionEvent event) {

                final TreeItem[] selection = tree.getSelection();
                if (selection != null && selection.length != 0) {
                    TreeItem item = selection[0];

                    try {
                        //FileObject file = (FileObject) item.getData(TREEITEMDATA_FILE);
                        String uri = (String) item.getData(TREEITEMDATA_URI);
                        String connectionId = (String) item.getData(TREEITEMDATA_CONNECTIONID);

                        shell.setText(getResourceString("Title.Resolving", new Object[] { uri }));

                        notifySelectedDirectory(resolveURI(uri, connectionId), connectionId);
                    } catch (FileSystemException e) {
                        debug(e); // "createTreeView.widgetSelected " +

                        VFSUtil.MessageBoxError(VFSUtil.getErrorMessageStack(e));
                        VFSUtil.setUiStateIdle(shell, getCurrentDirectory().getName().toString());
                    }
                }
            }

            public void widgetDefaultSelected(SelectionEvent event) {
                final TreeItem[] selection = tree.getSelection();
                if (selection != null && selection.length != 0) {
                    TreeItem item = selection[0];
                    String imageName;
                    if (item.getExpanded()) {
                        imageName = TREEITEMDATA_IMAGECOLLAPSED;
                    } else {
                        imageName = TREEITEMDATA_IMAGEEXPANDED;
                    }
                    item.setExpanded(!item.getExpanded());
                    final Image image = (Image) item.getData(imageName);
                    if (image != null)
                        item.setImage(image);
                    treeExpandItem(item);
                }
            }
        });
        tree.addTreeListener(new TreeAdapter() {
            public void treeExpanded(TreeEvent event) {
                final TreeItem item = (TreeItem) event.item;
                final Image image = (Image) item.getData(TREEITEMDATA_IMAGEEXPANDED);
                if (image != null)
                    item.setImage(image);
                treeExpandItem(item);
            }

            public void treeCollapsed(TreeEvent event) {
                final TreeItem item = (TreeItem) event.item;
                final Image image = (Image) item.getData(TREEITEMDATA_IMAGECOLLAPSED);
                if (image != null)
                    item.setImage(image);
            }
        });

        // Listen for key presses
        tree.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent event) {
                if (event.keyCode == 127) {
                    TreeItem item = tree.getSelection()[0];
                    String connectionId = (String) item.getData(TREEITEMDATA_CONNECTIONID);

                    // delete at the root level only
                    if (!(item.getParentItem() == null))
                        return;

                    if (connectionId.startsWith("file://"))
                        VFSUtil.MessageBoxInfo(getResourceString("remove.local.fs"));
                    else {
                        deleteAction.run(item);
                    }
                }
            }
        });

        createTreeDragSource(tree);
        createTreeDropTarget(tree);
    }

    /**
     * Creates the Drag & Drop DragSource for items being dragged from the tree.
     * 
     * @return the DragSource for the tree
     */
    private DragSource createTreeDragSource(final Tree tree) {
        DragSource dragSource = new DragSource(tree, DND.DROP_COPY); // | DND.DROP_MOVE 

        // Transfer data type is text (file URI)
        dragSource.setTransfer(new Transfer[] { FileTransfer.getInstance() });
        //dragSource.setTransfer(new Transfer[] { RTFTransfer.getInstance() });

        dragSource.addDragListener(new DragSourceListener() {
            TreeItem[] dndSelection = null;
            String[] sourceNames = null;

            public void dragStart(DragSourceEvent event) {
                dndSelection = tree.getSelection();
                sourceNames = null;
                event.doit = dndSelection.length > 0;
                isDragging = true;
                //            processedDropFiles = null;
            }

            public void dragFinished(DragSourceEvent event) {
                dragSourceHandleDragFinished(event, sourceNames);
                dndSelection = null;
                sourceNames = null;
                isDragging = false;
                //            processedDropFiles = null;
                //TODO            try {
                //               handleDeferredRefresh();
                //            } catch (Exception e) {
                //               debug ("createTreeDragSource.dragFinished " + e);
                //            }
            }

            public void dragSetData(DragSourceEvent event) {
                if (dndSelection == null || dndSelection.length == 0)
                    return;
                //if (! FileTransfer.getInstance().isSupportedType(event.dataType)) return;

                if (!FileTransfer.getInstance().isSupportedType(event.dataType)) {
                    //debug("createTreeDragSource.dragSetData File transfer data type is required");
                    return;
                }

                sourceNames = new String[dndSelection.length];
                FileObject file = null;
                String uri = null;
                String connectionId = null;
                for (int i = 0; i < dndSelection.length; i++) {
                    uri = (String) dndSelection[i].getData(TREEITEMDATA_URI);
                    connectionId = (String) dndSelection[i].getData(TREEITEMDATA_CONNECTIONID);
                    try {
                        file = resolveURI(uri, connectionId);
                    } catch (FileSystemException e) {
                        debug(e); //"createTreeDragSource.dragSetData " + 
                        continue;
                    }

                    /*
                     * In Order for FileTransfer to Work, the name must begin w/
                     * / in UNIX systems
                     */
                    sourceNames[i] = Os.isFamily(Os.OS_FAMILY_UNIX) ? FileName.SEPARATOR + file.toString()
                            : file.toString();

                    System.out.println("createTreeDragSource.dragSetData file path=" + sourceNames[i]);
                }
                //sourceNames[0] = connectionId;
                fSourceConnectionId = connectionId;
                event.data = sourceNames;
            }
        });
        return dragSource;
    }

    /**
     * Creates the Drag & Drop DropTarget for items being dropped onto the tree.
     * 
     * @return the DropTarget for the tree
     */
    private DropTarget createTreeDropTarget(final Tree tree) {
        DropTarget dropTarget = new DropTarget(tree, DND.DROP_COPY); // | DND.DROP_MOVE 

        dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });

        // Transfer data type is text 
        //dropTarget.setTransfer(new Transfer[] { RTFTransfer.getInstance()} );

        System.out.println("createTreeDropTarget");

        dropTarget.addDropListener(new DropTargetAdapter() {
            public void dragEnter(DropTargetEvent event) {
                isDropping = true;
            }

            public void dragLeave(DropTargetEvent event) {
                isDropping = false;
                //TODO            try {
                //               handleDeferredRefresh();
                //            } catch (Exception e) {
                //               debug("createTreeDropTarget.dragLeave " + e);
                //            }
            }

            public void dragOver(DropTargetEvent event) {
                dropTargetValidate(event, getTargetFile(event));
                event.feedback |= DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;
            }

            public void drop(DropTargetEvent event) {
                FileAndConnectionId targetFile = getTargetFile(event);

                try {
                    System.out.println("createTreeDropTarget.drop targetFile=" + targetFile.file);
                    if (dropTargetValidate(event, targetFile))
                        dropTargetHandleDrop(event, targetFile);

                } catch (FileSystemException e) {
                    VFSUtil.MessageBoxError(e.getMessage());
                } catch (Exception e1) {
                    debug(e1); // "createTreeDropTarget.drop " + 
                }
            }

            private FileAndConnectionId getTargetFile(DropTargetEvent event) {
                // Determine the target FileObject for the drop 
                TreeItem item = tree.getItem(tree.toControl(new Point(event.x, event.y)));
                FileObject targetFile = null;
                String connectionId = "";

                if (item != null) {
                    // We are over a particular item in the tree, use the item's file
                    //targetFile = (FileObject) item.getData(TREEITEMDATA_FILE);
                    String uri = (String) item.getData(TREEITEMDATA_URI);
                    connectionId = (String) item.getData(TREEITEMDATA_CONNECTIONID);
                    try {
                        targetFile = resolveURI(uri, connectionId);
                    } catch (Exception e) {
                        debug(e); //"createTreeDropTarget.getTargetFile " + 
                    }
                }
                return new FileAndConnectionId(targetFile, connectionId);
            }
        });
        return dropTarget;
    }

    /**
     * Handles expand events on a tree item.
     * 
     * @param item the TreeItem to fill in
     */
    private void treeExpandItem(TreeItem item) {
        shell.setCursor(CFPluginImages.cursors[CFPluginImages.cursorWait]);
        final Object stub = item.getData(TREEITEMDATA_STUB);

        if (stub == null) {
            try {
                treeRefreshItem(item, true);
            } catch (Exception e) {
                debug(e); // "treeExpandItem " + 
            }
        }
        shell.setCursor(CFPluginImages.cursors[CFPluginImages.cursorDefault]);
    }

    /**
     * Add a file sytem node
     * @param filesystem
     */
    public void addFileSystem(String connectionId) {
        try {
            TreeItem newItem = new TreeItem(tree, SWT.NULL);
            treeInitVolume(newItem, connectionId);// masterFile);

            //   placeholder child item to get "expand" button
            new TreeItem(newItem, SWT.NULL);

        } catch (Exception e) {
            VFSUtil.MessageBoxInfo(e.getMessage());
        }
    }

    /**
     * Traverse the entire tree and update only what has changed.
     * 
     * @param roots the root directory listing
     */
    private void treeRefresh(FileObject[] masterFiles) {
        TreeItem[] items = tree.getItems();
        int masterIndex = 0;
        int itemIndex = 0;

        for (int i = 0; i < items.length; ++i) {
            final TreeItem item = items[i];
            //         final String uri = (String)item.getData(TREEITEMDATA_URI);
            final FileObject itemFile = null;

            if ((itemFile == null) || (masterIndex == masterFiles.length)) {
                // remove bad item or placeholder
                item.dispose();
                continue;
            }
            final FileObject masterFile = masterFiles[masterIndex];

            int compare = VFSUtil.compareFiles(masterFile, itemFile);

            if (compare == 0) {
                // same file, update it
                try {
                    treeRefreshItem(item, false);
                } catch (Exception e) {
                    error("treeRefresh.treeRefreshItem " + e);
                }

                ++itemIndex;
                ++masterIndex;
            } else if (compare < 0) {
                // should appear before file, insert it
                TreeItem newItem = new TreeItem(tree, SWT.NULL, itemIndex);
                treeInitVolume(newItem, masterFile.getName().toString());// masterFile);
                new TreeItem(newItem, SWT.NULL); // placeholder child item to get "expand" button
                ++itemIndex;
                ++masterIndex;
                --i;
            } else {
                // should appear after file, delete stale item
                item.dispose();
            }
        }

        for (; masterIndex < masterFiles.length; ++masterIndex) {
            final FileObject masterFile = masterFiles[masterIndex];
            TreeItem newItem = new TreeItem(tree, SWT.NULL);

            //System.out.println("treeRefresh file=" + masterFile.getName().getURI());
            treeInitVolume(newItem, masterFile.getName().toString());// masterFile);
            new TreeItem(newItem, SWT.NULL); // placeholder child item to get "expand" button
        }
    }

    /**
     * Insert URIS from $HOME/.vfs into the Tree
     */
    private void treeInsertRemoteURIs() {
        // Add remote URI nodes to the tree

        if (fConnections != null) {
            Iterator it = fConnections.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry pairs = (Map.Entry) it.next();
                FTPConnectionProperties conProps = (FTPConnectionProperties) pairs.getValue();
                if (!conProps.getURI().equals(conProps.getConnectionid())) {
                    VFSUtil.treeAddFsNode(tree, conProps);
                }
            }
        }
    }

    public void treeRefreshRemoteURIs() {
        fConnections = VFSUtil.loadFileSystems();
        try {
            treeRefresh(getRoots(fsManager));
        } catch (FileSystemException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        if (fConnections != null) {
            Iterator it = fConnections.entrySet().iterator();
            TreeItem[] items = tree.getItems();
            int masterIndex = 0;
            int itemIndex = 0;

            while (it.hasNext()) {
                boolean foundConnection = false;
                Map.Entry pairs = (Map.Entry) it.next();
                FTPConnectionProperties conProps = (FTPConnectionProperties) pairs.getValue();
                for (int i = 0; i < items.length; ++i) {
                    final TreeItem item = items[i];
                    final String treeConnectionId = (String) item.getData(TREEITEMDATA_CONNECTIONID);
                    final String filePath = (String) item.getData(TREEITEMDATA_FILE);
                    if (treeConnectionId.equals(conProps.getConnectionid())) {
                        foundConnection = true;
                        break;
                    } else {
                        if (fConnections.get(treeConnectionId) == null && filePath != treeConnectionId) {
                            item.dispose();
                        }
                    }

                }
                if (!foundConnection) {
                    VFSUtil.treeAddFsNode(tree, conProps);
                }
            }
        }
    }

    /**
     * Traverse an item in the tree and update only what has changed.
     * 
     * @param dirItem the tree item of the directory
     * @param forcePopulate true iff we should populate non-expanded items as well
     */
    private void treeRefreshItem(TreeItem dirItem, boolean forcePopulate) throws FileSystemException {
        final String uri = (String) dirItem.getData(TREEITEMDATA_URI);

        //System.out.println("treeRefreshItem uri=" + uri);

        if (!forcePopulate && !dirItem.getExpanded()) {
            // Refresh non-expanded item
            if (dirItem.getData(TREEITEMDATA_STUB) != null) {
                treeItemRemoveAll(dirItem);
                new TreeItem(dirItem, SWT.NULL); // placeholder child item to get "expand" button
                dirItem.setData(TREEITEMDATA_STUB, null);
            }
            return;
        }
        // Refresh expanded item
        dirItem.setData(TREEITEMDATA_STUB, this); // clear stub flag

        /* Get directory listing */
        FileObject[] subFiles = (uri != null)
                ? getDirectoryList(resolveURI(uri, (String) dirItem.getData(TREEITEMDATA_CONNECTIONID)))
                : null;

        if (subFiles == null || subFiles.length == 0) {
            /* Error or no contents */
            treeItemRemoveAll(dirItem);
            dirItem.setExpanded(false);
            return;
        }

        /* Refresh sub-items */
        TreeItem[] items = dirItem.getItems();
        final FileObject[] masterFiles = subFiles;

        int masterIndex = 0;
        int itemIndex = 0;
        FileObject masterFile = null;

        String folderBaseName = null;

        for (int i = 0; i < items.length; ++i) {
            while ((masterFile == null) && (masterIndex < masterFiles.length)) {
                masterFile = masterFiles[masterIndex++];

                //if (! masterFile.isDirectory()) masterFile = null;
                if (!VFSUtil.isDirectory(masterFile))
                    masterFile = null;
            }

            final TreeItem item = items[i];
            //         final String itemUri = (String) item.getData(TREEITEMDATA_URI);
            //System.out.println("itemUri=" + itemUri);         
            final String itemFile = (String) item.getData(TREEITEMDATA_URI);
            //final FileObject itemFile = (FileObject) item.getData(TREEITEMDATA_FILE);

            if ((itemFile == null) || (masterFile == null)) {
                // remove bad item or placeholder
                item.dispose();
                continue;
            }
            int compare = VFSUtil.compareFiles(masterFile,
                    resolveURI(itemFile, (String) item.getData(TREEITEMDATA_CONNECTIONID)));

            if (compare == 0) {
                // same file, update it
                treeRefreshItem(item, false);
                masterFile = null;
                ++itemIndex;
            } else if (compare < 0) {
                // should appear before file, insert it
                TreeItem newItem = new TreeItem(dirItem, SWT.NULL, itemIndex);

                folderBaseName = masterFile.getName().getBaseName();

                //treeInitFolder(newItem, folderBaseName , uri + FileName.SEPARATOR + folderBaseName); //masterFile);
                treeInitFolder(newItem, folderBaseName, masterFile.toString());

                new TreeItem(newItem, SWT.NULL); // add a placeholder child item so we get the "expand" button
                masterFile = null;
                ++itemIndex;
                --i;
            } else {
                // should appear after file, delete stale item
                item.dispose();
            }
        }
        while ((masterFile != null) || (masterIndex < masterFiles.length)) {
            if (masterFile != null) {
                TreeItem newItem = new TreeItem(dirItem, SWT.NULL);

                folderBaseName = masterFile.getName().getBaseName();

                //treeInitFolder(newItem, folderBaseName , uri + FileName.SEPARATOR + folderBaseName );
                treeInitFolder(newItem, folderBaseName, masterFile.toString());

                new TreeItem(newItem, SWT.NULL); // add a placeholder child item so we get the "expand" button
                if (masterIndex == masterFiles.length)
                    break;
            }
            masterFile = masterFiles[masterIndex++];

            if (!VFSUtil.isDirectory(masterFile)) {
                masterFile = null;
            }
        }
    }

    /**
     * Foreign method: removes all children of a TreeItem.
     * @param treeItem the TreeItem
     */
    private static void treeItemRemoveAll(TreeItem treeItem) {
        final TreeItem[] children = treeItem.getItems();
        for (int i = 0; i < children.length; ++i) {
            children[i].dispose();
        }
    }

    /**
     * Initializes a folder item.
     * 
     * @param item the TreeItem to initialize
     * @param folder the FileObject associated with this TreeItem
     */
    private void treeInitFolder(TreeItem item, String folder, String uri) {
        item.setText(folder);
        item.setImage(iconCache.get(CFPluginImages.ICON_FOLDER));
        item.setData(TREEITEMDATA_FILE, folder);
        item.setData(TREEITEMDATA_URI, uri);
        item.setData(TREEITEMDATA_CONNECTIONID, item.getParentItem().getData(TREEITEMDATA_CONNECTIONID));
        item.setData(TREEITEMDATA_IMAGEEXPANDED, iconCache.get(CFPluginImages.ICON_FOLDER_OPEN));
        item.setData(TREEITEMDATA_IMAGECOLLAPSED, iconCache.get(CFPluginImages.ICON_FOLDER));
    }

    /**
     * Initializes a volume item.
     * 
     * @param item the TreeItem to initialize
     * @param volume the File associated with this TreeItem
     */
    private void treeInitVolume(TreeItem item, String connectionId) {//FileObject volume) {
        FTPConnectionProperties connectionProperties = fConnections.get(connectionId);
        if (connectionProperties == null) {
            addLocalConnection(connectionId);
            connectionProperties = fConnections.get(connectionId);
        }
        item.setText(connectionId);
        item.setImage(iconCache.get(CFPluginImages.ICON_DRIVE));
        item.setData(TREEITEMDATA_FILE, connectionProperties.getConnectionid());
        item.setData(TREEITEMDATA_URI, connectionProperties.getURI());
        item.setData(TREEITEMDATA_CONNECTIONID, connectionProperties.getConnectionid());
        item.setData(TREEITEMDATA_IMAGEEXPANDED, iconCache.get(CFPluginImages.ICON_DRIVE_FTP));
        item.setData(TREEITEMDATA_IMAGECOLLAPSED, iconCache.get(CFPluginImages.ICON_DRIVE));
    }

    /**
     * Creates the file details table.
     * 
     * @param parent the parent control
     */
    private void createTableView(Composite parent) {
        //Composite composite = new Composite(parent, SWT.NONE);
        TabFolder composite = new TabFolder(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout();

        gridLayout.numColumns = 1;
        gridLayout.marginHeight = gridLayout.marginWidth = 2;
        gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 0;
        composite.setLayout(gridLayout);

        tabFileItems = new TabItem(composite, SWT.CLOSE);

        //      tableContentsOfLabel = new Label(composite, SWT.BORDER);
        //      tableContentsOfLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL));

        table = new Table(composite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
        table.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL));

        //ColumnViewerToolTipSupport.enableFor(table);
        // Tooltips
        //      final ToolTipHandler tooltip = new ToolTipHandler(parent.getParent().getShell());
        //      tooltip.activateHoverHelp(table);

        tabFileItems.setControl(table);

        for (int i = 0; i < tableTitles.length; ++i) {
            TableColumn column = new TableColumn(table, SWT.NONE);
            column.setText(tableTitles[i]);
            column.setWidth(tableWidths[i]);
        }
        table.setHeaderVisible(true);
        // Listen for key presses
        table.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent event) {
                // Del key pressed
                if (event.keyCode == 127) {
                    FileObject[] files = getSelectedFiles();
                    int count = files.length;

                    // set a single delete or multiple delete text message
                    String message = (count == 1)
                            ? getResourceString("msg.box.delete.uri", new Object[] { files[0] })
                            : getResourceString("msg.box.delete.resources",
                                    new Object[] { new Integer(count), getCurrentDirectory() });

                    if (VFSUtil.MessageBoxYesNo(message)) {
                        deleteObjects(files);

                        // Refresh
                        doRefresh();
                    }
                }
            }

            private FileObject[] getSelectedFiles() {
                final TableItem[] items = table.getSelection();
                final FileObject[] files = new FileObject[items.length];

                for (int i = 0; i < items.length; ++i) {
                    files[i] = (FileObject) items[i].getData(TABLEITEMDATA_FILE);
                }
                return files;
            }
        });

        // Listen for selections
        table.addSelectionListener(new SelectionAdapter() {
            // Fires when item is clicked
            public void widgetSelected(SelectionEvent event) {
                try {
                    notifySelectedFiles(getSelectedFiles());
                } catch (Exception e) {
                    //debug ("createTableView.widgetSelected " + e);
                }
            }

            // fires when list item is selected (double clicked)
            public void widgetDefaultSelected(SelectionEvent event) {
                doDefaultFileAction(getSelectedFiles());
            }

            private FileObject[] getSelectedFiles() {
                final TableItem[] items = table.getSelection();
                final FileObject[] files = new FileObject[items.length];

                for (int i = 0; i < items.length; ++i) {
                    files[i] = (FileObject) items[i].getData(TABLEITEMDATA_FILE);
                }
                return files;
            }
        });

        createTableDragSource(table);
        createTableDropTarget(table);
    }

    /**
     * Creates the Drag & Drop DragSource for items being dragged from the table.
     * 
     * @return the DragSource for the table
     */
    private DragSource createTableDragSource(final Table table) {
        DragSource dragSource = new DragSource(table, DND.DROP_COPY); // | DND.DROP_MOVE

        // transfer type is text data (file URI)
        dragSource.setTransfer(new Transfer[] { FileTransfer.getInstance() });

        dragSource.addDragListener(new DragSourceListener() {
            TableItem[] dndSelection = null;
            String[] sourceNames = null;

            public void dragStart(DragSourceEvent event) {
                dndSelection = table.getSelection();
                sourceNames = null;
                event.doit = dndSelection.length > 0;
                isDragging = true;
            }

            public void dragFinished(DragSourceEvent event) {
                dragSourceHandleDragFinished(event, sourceNames);
                dndSelection = null;
                sourceNames = null;
                isDragging = false;

                //TODO            try {
                //               handleDeferredRefresh();
                //            } catch (Exception e) {
                //               debug("createTableDragSource.dragFinished " + e);
                //            }

            }

            public void dragSetData(DragSourceEvent event) {
                if (dndSelection == null || dndSelection.length == 0)
                    return;

                if (!FileTransfer.getInstance().isSupportedType(event.dataType)) {
                    error("createTableDragSource.dragSetData File Transfer data type text is required.");
                    return;
                }

                // Cannot put Objects in the transfer event data field, they must be path
                // and begin with / in UNIX
                sourceNames = new String[dndSelection.length + 1];
                String connectionId = "";
                for (int i = 1; i < dndSelection.length; i++) {
                    FileObject file = (FileObject) dndSelection[i].getData(TABLEITEMDATA_FILE);
                    connectionId = (String) dndSelection[i].getData(TABLEITEMDATA_CONNECTIONID);
                    /*
                     * In Order for FileTransfer to Work, the name must begin w/
                     * / in UNIX systems
                     */
                    sourceNames[i] = Os.isFamily(Os.OS_FAMILY_UNIX) ? FileName.SEPARATOR + file.toString()
                            : file.toString();

                    System.out.println(
                            "createTableDragSource::dragSetData: sourceNames [" + i + "] path= " + sourceNames[i]);

                }
                sourceNames[0] = connectionId;
                event.data = sourceNames;
            }
        });
        return dragSource;
    }

    /**
     * Creates the Drag & Drop DropTarget for items being dropped onto the table.
     * 
     * @return the DropTarget for the table
     */
    private DropTarget createTableDropTarget(final Table table) {
        DropTarget dropTarget = new DropTarget(table, DND.DROP_COPY); // | DND.DROP_MOVE

        dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
        //dropTarget.setTransfer(new Transfer[] { RTFTransfer.getInstance() });

        dropTarget.addDropListener(new DropTargetAdapter() {
            public void dragEnter(DropTargetEvent event) {
                isDropping = true;
            }

            public void dragLeave(DropTargetEvent event) {
                isDropping = false;

                // TODO            try {
                //               handleDeferredRefresh();
                //            } catch (Exception e) {
                //               debug("createTableDropTarget.dragLeave " + e);
                //            }

            }

            public void dragOver(DropTargetEvent event) {
                dropTargetValidate(event, getTargetFile(event));
                event.feedback |= DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;
            }

            public void drop(DropTargetEvent event) {
                FileAndConnectionId targetFile = getTargetFile(event);
                try {
                    if (dropTargetValidate(event, targetFile))
                        dropTargetHandleDrop(event, targetFile);

                } catch (Exception e) {
                    debug(e); // "createTableDropTarget.drop " + 
                }
            }

            private FileAndConnectionId getTargetFile(DropTargetEvent event) {
                // Determine the target FileObject for the drop 
                TableItem item = table.getItem(table.toControl(new Point(event.x, event.y)));
                FileObject targetFile = null;
                String connectionId = "";
                if (item == null) {
                    // We are over an unoccupied area of the table.
                    // If it is a COPY, we can use the table's root file.
                    if (event.detail == DND.DROP_COPY) {
                        targetFile = (FileObject) table.getData(TABLEDATA_DIR);
                        connectionId = (String) table.getData(TABLEDATA_CONNECTIONID);
                    }
                } else {
                    // We are over a particular item in the table, use the item's file
                    targetFile = (FileObject) item.getData(TABLEITEMDATA_FILE);
                    connectionId = (String) table.getData(TABLEITEMDATA_CONNECTIONID);
                }
                return new FileAndConnectionId(targetFile, connectionId);
            }
        });
        return dropTarget;
    }

    /**
     * Notifies the application components that a new current directory has been selected
     * 
     * @param dir the directory that was selected, null is ignored
     */
    public void notifySelectedDirectory(FileObject dir, String connectionId) throws FileSystemException {
        if (dir == null || connectionId == null)
            return;
        if (getCurrentDirectory() != null && dir.equals(getCurrentDirectory()))
            return;

        setCurrentDirectory(dir);
        setCurrentConnectionId(connectionId);

        //System.err.println("notifySelectedDirectory currentDirectory:" 
        //      + currentDirectory + " notify dir:" + dir );

        // Processing...
        VFSUtil.setUiStateListContents(shell, getCurrentDirectory().getName().toString());

        notifySelectedFiles(null);

        /* Table view:
         * Displays the contents of the selected directory.
         */
        workerUpdate(dir, connectionId, false);

        /* Combo view:
         * Sets the combo box to point to the selected directory.
         */
        final FileObject[] comboRoots = (FileObject[]) combo.getData(COMBODATA_ROOTS);
        int comboEntry = -1;
        if (comboRoots != null) {
            for (int i = 0; i < comboRoots.length; ++i) {
                if (dir.equals(comboRoots[i])) {
                    comboEntry = i;
                    break;
                }
            }
        }

        if (comboEntry == -1)
            combo.setText(dir.getName().toString());
        else
            combo.select(comboEntry);

        /* Tree view:
         * If not already expanded, recursively expands the parents of the specified
         * directory until it is visible.
         */
        Vector /* of FileObject */ path = new Vector();

        // Build a stack of paths from the root of the tree
        while (dir != null) {
            path.add(dir);
            //dir = dir.getParentFile();

            try {
                dir = dir.getParent();
            } catch (Exception e) {
                System.err.println("notifySelectedDirectory:" + e);
            }
        }

        //System.out.println("notifySelectedDirectory currentDirectory:" 
        //      + currentDirectory + " dir:" + dir + " tree path:" + path);

        // Recursively expand the tree to get to the specified directory
        TreeItem[] items = tree.getItems();
        TreeItem lastItem = null;
        String destConnectionId = null;
        TreeItem item = null;
        // filter by connection first
        for (int k = 0; k < items.length; ++k) {
            item = items[k];
            destConnectionId = (String) item.getData(TREEITEMDATA_CONNECTIONID);
            if (connectionId.equals(connectionId)) {
                items = items[k].getItems();
            }
        }

        for (int i = path.size() - 1; i >= 0; --i) {
            final FileObject pathElement = (FileObject) path.elementAt(i);

            // Search for a particular FileObject in the array of tree items
            // No guarantee that the items are sorted in any recognizable fashion, so we'll
            // just sequential scan.  There shouldn't be more than a few thousand entries.
            item = null;
            for (int k = 0; k < items.length; ++k) {
                item = items[k];
                if (item.isDisposed())
                    continue;

                final String fileURI = ((String) item.getData(TREEITEMDATA_URI));
                destConnectionId = (String) item.getData(TREEITEMDATA_CONNECTIONID);

                // Resolving the children will attempt a server connection. This will hang the UI if the server is down
                // Thus perform a comparison by Host/Scheme/Path only
                // Cannot just compare the URI strings cuz FileObject hides the port number

                if (connectionId.equals(destConnectionId) && VFSUtil.compareURIs(fileURI, pathElement.toString()))
                    break;
            }

            if (item == null)
                break;
            lastItem = item;

            if (i != 0 && !item.getExpanded()) {
                treeExpandItem(item);
                item.setExpanded(true);
            }
            items = item.getItems();
        }
        if (connectionId.equals(destConnectionId)) {
            tree.setSelection((lastItem != null) ? new TreeItem[] { lastItem } : new TreeItem[0]);
        }

        /* Shell:
         * Sets the title to indicate the selected directory
         */
        VFSUtil.setUiStateIdle(shell, VFSUtil.stripUserTokens(getCurrentDirectory().toString()));
    }

    /**
     * Notifies the application components that files have been selected
     * 
     * @param files the files that were selected, null or empty array indicates no active selection
     */
    void notifySelectedFiles(FileObject[] files) throws FileSystemException {
        /* Details:
         * Update the details that are visible on screen.
         */
        if ((files != null) && (files.length != 0)) {
            numObjectsLabel.setText(getResourceString("details.NumberOfSelectedFiles.text",
                    new Object[] { new Integer(files.length) }));

            long fileSize = 0L;

            for (int i = 0; i < files.length; ++i) {
                //fileSize += files[i].length();
                fileSize += files[i].getContent().getSize();
            }

            diskSpaceLabel.setText(getResourceString("details.FileSize.text", new Object[] { new Long(fileSize) }));
        } else {
            // No files selected
            diskSpaceLabel.setText("");
            if (getCurrentDirectory() != null) {
                int numObjects = getDirectoryList(getCurrentDirectory()).length;

                numObjectsLabel.setText(getResourceString("details.DirNumberOfObjects.text",
                        new Object[] { new Integer(numObjects) }));
            } else {
                numObjectsLabel.setText("");
            }
        }
    }

    /**
     * Notifies the application components that files must be refreshed
     * 
     * @param files the files that need refreshing, empty array is a no-op, null refreshes all
     */
    public void notifyRefreshFiles(FileObject[] files) throws FileSystemException {
        if (files != null && files.length == 0)
            return;

        if ((deferredRefreshRequested) && (deferredRefreshFiles != null) && (files != null)) {
            // merge requests
            FileObject[] newRequest = new FileObject[deferredRefreshFiles.length + files.length];
            System.arraycopy(deferredRefreshFiles, 0, newRequest, 0, deferredRefreshFiles.length);
            System.arraycopy(files, 0, newRequest, deferredRefreshFiles.length, files.length);
            deferredRefreshFiles = newRequest;
        } else {
            deferredRefreshFiles = files;
            deferredRefreshRequested = true;
        }

        handleDeferredRefresh();
    }

    /**
     * Handles deferred Refresh notifications (due to Drag & Drop)
     */
    void handleDeferredRefresh() throws FileSystemException {
        //System.out.println("handleDeferredRefresh");

        if (isDragging || isDropping || !deferredRefreshRequested)
            return;

        deferredRefreshRequested = false;
        FileObject[] files = deferredRefreshFiles;
        deferredRefreshFiles = null;

        shell.setCursor(CFPluginImages.cursors[CFPluginImages.cursorWait]);

        /* Table view:
         * Refreshes information about any files in the list and their children.
         */
        boolean refreshTable = false;

        if (files != null) {
            for (int i = 0; i < files.length; ++i) {
                final FileObject file = files[i];
                if (file.equals(getCurrentDirectory())) {
                    refreshTable = true;
                    break;
                }

                FileObject parentFile = file.getParent();

                if ((parentFile != null) && (parentFile.equals(getCurrentDirectory()))) {
                    refreshTable = true;
                    break;
                }
            }
        } else
            refreshTable = true;

        if (refreshTable)
            workerUpdate(getCurrentDirectory(), getCurrentConnectionId(), true);

        /* Combo view:
         * Refreshes the list of roots
         */
        final FileObject[] roots = getRoots(fsManager);

        if (files == null) {
            boolean refreshCombo = false;
            final FileObject[] comboRoots = (FileObject[]) combo.getData(COMBODATA_ROOTS);

            if ((comboRoots != null) && (comboRoots.length == roots.length)) {
                for (int i = 0; i < roots.length; ++i) {
                    if (!roots[i].equals(comboRoots[i])) {
                        refreshCombo = true;
                        break;
                    }
                }
            } else
                refreshCombo = true;

            if (refreshCombo) {
                combo.removeAll();
                combo.setData(COMBODATA_ROOTS, roots);

                for (int i = 0; i < roots.length; ++i) {
                    final FileObject file = roots[i];
                    combo.add(file.getName().toString());
                }
            }
        }

        /* Tree view:
         * Refreshes information about any files in the list and their children.
         */
        treeRefresh(roots);

        treeInsertRemoteURIs();

        // Remind everyone where we are in the filesystem
        final FileObject dir = getCurrentDirectory();
        setCurrentDirectory(null);
        final String con = getCurrentConnectionId();
        setCurrentConnectionId(null);
        notifySelectedDirectory(dir, con);

        shell.setCursor(CFPluginImages.cursors[CFPluginImages.cursorDefault]);
    }

    /**
     * Performs the default action on a set of files.
     * 
     * @param files the array of files to process
     */
    void doDefaultFileAction(FileObject[] files) {
        // only uses the 1st file (for now)
        if (files.length == 0)
            return;
        final FileObject file = files[0];

        if (VFSUtil.isDirectory(file)) {
            try {
                notifySelectedDirectory(file, getCurrentConnectionId());
            } catch (Exception e) {
                debug(e); // "FileViewer.doDefaultFileAction " + 
            }
        } else {
            //final String fileName = file.getAbsolutePath();
            RemoteFileEditorInput input = new RemoteFileEditorInput(file);
            try {
                IEditorDescriptor desc = PlatformUI.getWorkbench().getEditorRegistry()
                        .getDefaultEditor(file.getName().getBaseName());
                IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
                //page.openEditor(input, desc.getId());

                page.openEditor(input, CFMLEditor.ID);

                // wonder how this could work?
                //            IFileStore fileStore;
                //            fileStore = EFS.getFileSystem(file.getName().getScheme()).getStore(file.getURL().toURI());
                //            IDE.openEditorOnFileStore( page, fileStore );
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            //         if (! Program.launch(fileName)) {   
            //            MessageBox dialog = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
            //            dialog.setMessage(getResourceString("error.FailedLaunch.message", new Object[] { fileName }));
            //            dialog.setText(shell.getText ());
            //            dialog.open();
            //         }
        }
    }

    /**
     * Navigates to the parent directory
     */
    void doParent() {
        if (getCurrentDirectory() == null)
            return;
        //FileObject parentDirectory = currentDirectory.getParentFile();
        try {
            FileObject parentDirectory = getCurrentDirectory().getParent();
            notifySelectedDirectory(parentDirectory, getCurrentConnectionId());
        } catch (Exception e) {
            System.err.println("FileViewer::doParent " + e);
        }
    }

    /**
     * Performs a refresh
     */
    void doRefresh() {
        try {
            notifyRefreshFiles(null);
        } catch (Exception e) {
            debug(e); // "FileViewer.doRefresh " + 
        }
    }

    /**
     * Validates a drop target as a candidate for a drop operation.
     * <p>
     * Used in dragOver() and dropAccept().<br>
     * Note event.detail is set to DND.DROP_NONE by this method if the target is not valid.
     * </p>
     * @param event the DropTargetEvent to validate
     * @param fileAndConnectionId the FileObject representing the drop target location
     *        under inspection, or null if none
     */
    private boolean dropTargetValidate(DropTargetEvent event, FileAndConnectionId fileAndConnectionId) {
        //if (targetFile != null && targetFile.isDirectory()) {
        if (fileAndConnectionId.file != null && VFSUtil.isDirectory(fileAndConnectionId.file)) {
            if (event.detail != DND.DROP_COPY && event.detail != DND.DROP_MOVE) {
                event.detail = DND.DROP_COPY; // DND.DROP_MOVE;
            }
        } else {
            event.detail = DND.DROP_NONE;
        }
        return event.detail != DND.DROP_NONE;
    }

    /**
     * Handles a drop on a dropTarget.
     * <p>
     * Used in drop().<br>
     * Note event.detail is modified by this method.
     * </p>
     * @param event the DropTargetEvent passed as parameter to the drop() method
     * @param targetFile the FileObject representing the drop target location
     *        under inspection, or null if none
     */
    private void dropTargetHandleDrop(DropTargetEvent event, FileAndConnectionId fileAndConnectionId)
            throws IOException {

        // Get dropped data (an array of filenames)
        //      if (! dropTargetValidate(event, targetFile)) 
        //         return;

        final String[] sourceNames = (String[]) event.data;

        if (sourceNames == null)
            event.detail = DND.DROP_NONE;
        if (event.detail == DND.DROP_NONE)
            return;
        VFSUtil.copyFiles(fsManager, sourceNames, fConnections.get(fSourceConnectionId), fileAndConnectionId.file,
                fConnections.get(fileAndConnectionId.connectionId));
        //VFSUtil.copyFiles(fsManager, sourceNames, fileAndConnectionId.file);

        // refresh
        notifyRefreshFiles(new FileObject[] { fileAndConnectionId.file });

    }

    /**
     * Handles the completion of a drag on a dragSource.
     * <p>
     * Used in dragFinished().<br>
     * </p>
     * @param event the DragSourceEvent passed as parameter to the dragFinished() method
     * @param sourceNames the names of the files that were dragged (event.data is invalid)
     * @param sourceConnectionId 
     */
    private void dragSourceHandleDragFinished(DragSourceEvent event, String[] sourceNames) {
        if (sourceNames == null)
            return;
        if (event.detail != DND.DROP_MOVE)
            return;

        // Get array of files that were actually transferred
        // Do nothing w/ source files
    }

    /**
     * Gets filesystem root entries
     * @param fsManager
     * @return an array of Files corresponding to the root directories on the platform,
     *         may be empty but not null
     */
    FileObject[] getRoots(FileSystemManager fsManager) throws FileSystemException {
        /*
         * On JDK 1.22 only...
         */
        // return File.listRoots();
        FileObject[] roots = null;
        //      FileObject[] newRequest = null;
        /*
         * On JDK 1.1.7 and beyond...
         * -- PORTABILITY ISSUES HERE --
         */
        if (System.getProperty("os.name").indexOf("Windows") != -1) {
            Vector /* of FileObject */ list = new Vector();
            list.add(fsManager.resolveFile(DRIVE_A));

            for (char i = 'c'; i <= 'z'; ++i) {
                //FileObject drive = new FileObject(i + ":" + FileName.SEPARATOR);
                FileObject drive = fsManager.resolveFile(i + ":" + FileName.SEPARATOR);

                if (VFSUtil.isDirectory(drive) && drive.exists()) {
                    list.add(drive);
                    if (initial && i == 'c') {
                        setCurrentDirectory(drive);
                        setCurrentConnectionId(drive.getName().toString());
                        initial = false;
                    }
                }
            }
            roots = (FileObject[]) list.toArray(new FileObject[list.size()]);
            VFSUtil.sortFiles(roots);

            //return roots;
        } else {
            FileObject root = fsManager.resolveFile(FileName.SEPARATOR);

            if (initial) {
                setCurrentDirectory(root);
                setCurrentConnectionId(root.getName().toString());
                initial = false;
            }
            roots = new FileObject[] { root };
        }

        return roots; //newRequest; 
    }

    private void addLocalConnection(String connectionId) {
        FTPConnectionProperties newConProps = new FTPConnectionProperties(connectionId);
        newConProps.setURI(connectionId);
        fConnections.put(connectionId, newConProps);
    }

    /*
     * This worker updates the table with file information in the background.
     * <p>
     * Implementation notes:
     * <ul>
     * <li> It is designed such that it can be interrupted cleanly.
     * <li> It uses asyncExec() in some places to ensure that SWT Widgets are manipulated in the
     *      right thread.  Exclusive use of syncExec() would be inappropriate as it would require a pair
     *      of context switches between each table update operation.
     * </ul>
     * </p>
     */

    /**
     * Stops the worker and waits for it to terminate.
     */
    void workerStop() {
        if (workerThread == null)
            return;
        synchronized (workerLock) {
            workerCancelled = true;
            workerStopped = true;
            workerLock.notifyAll();
        }
        while (workerThread != null) {
            if (!display.readAndDispatch())
                display.sleep();
        }
    }

    /**
     * Notifies the worker that it should update itself with new data.
     * Cancels any previous operation and begins a new one.
     * 
     * @param dir the new base directory for the table, null is ignored
     * @param currentConnectionId 
     * @param force if true causes a refresh even if the data is the same
     */
    void workerUpdate(FileObject dir, String connectionId, boolean force) {
        if (dir == null)
            return;
        if ((!force) && (workerNextDir != null) && (workerNextDir.equals(dir)))
            return;

        synchronized (workerLock) {
            workerNextDir = dir;
            workerNextConnectionId = connectionId;
            workerStopped = false;
            workerCancelled = true;
            workerLock.notifyAll();
        }
        if (workerThread == null) {
            workerThread = new Thread(workerRunnable);
            workerThread.start();
        }
    }

    /**
     * Manages the worker's thread
     */
    private final Runnable workerRunnable = new Runnable() {
        public void run() {
            while (!workerStopped) {
                synchronized (workerLock) {
                    workerCancelled = false;
                    workerStateDir = workerNextDir;
                    workerStateConnectionId = workerNextConnectionId;
                }

                workerExecute();

                synchronized (workerLock) {
                    try {
                        if ((!workerCancelled) && (workerStateDir == workerNextDir))
                            workerLock.wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            workerThread = null;
            // wake up UI thread in case it is in a modal loop awaiting thread termination
            // (see workerStop())
            display.wake();
        }
    };

    /**
     * Updates the table's contents
     */
    private void workerExecute() {
        FileObject[] dirList;

        // Clear existing information
        display.syncExec(new Runnable() {
            public void run() {
                tabFileItems.setText(VFSView.getResourceString("details.ContentsOf.text",
                        new Object[] { VFSUtil.stripUserTokens(workerStateDir.toString()) }));

                table.removeAll();
                table.setData(TABLEDATA_DIR, workerStateDir);
                table.setData(TABLEDATA_CONNECTIONID, workerStateConnectionId);
            }
        });

        try {
            dirList = getDirectoryList(workerStateDir);

            for (int i = 0; (!workerCancelled) && (i < dirList.length); i++) {
                workerAddFileDetails(dirList[i], workerStateConnectionId);
            }

        } catch (FileSystemException e) {
            VFSUtil.MessageBoxError(e.getMessage());
        }
    }

    /**
     * Adds a file's detail information to the directory list
     */
    private void workerAddFileDetails(final FileObject file, final String connectionId) {
        long date = 0;
        try {
            date = file.getContent().getLastModifiedTime();
        } catch (Exception e) {
        }

        final String nameString = file.getName().getBaseName();
        final String dateString = dateFormat.format(new Date(date));
        final String sizeString;
        final String typeString;
        final Image iconImage;

        // If directory
        if (VFSUtil.isDirectory(file)) {
            typeString = getResourceString("filetype.Folder");
            sizeString = "";
            iconImage = iconCache.get(CFPluginImages.ICON_FOLDER);
        } else {
            long size = 0;
            try {
                size = file.getContent().getSize();
            } catch (Exception e) {
            }

            // It is a file, get size
            sizeString = getResourceString("filesize.KB", new Object[] { new Long((size + 512) / 1024) });

            int dot = nameString.lastIndexOf('.');
            if (dot != -1) {
                String extension = nameString.substring(dot);
                Program program = Program.findProgram(extension);

                // get icon based on extension
                if (program != null) {
                    typeString = program.getName();
                    iconImage = CFPluginImages.getIconFromProgram(program);
                } else {
                    typeString = getResourceString("filetype.Unknown", new Object[] { extension.toUpperCase() });
                    iconImage = iconCache.get(CFPluginImages.ICON_FILE);
                }
            } else {
                typeString = getResourceString("filetype.None");
                iconImage = iconCache.get(CFPluginImages.ICON_FILE);
            }
        }

        // Table values: Name, Size, Type, Date
        final String[] strings = new String[] { nameString, sizeString, typeString, dateString };

        display.syncExec(new Runnable() {
            public void run() {
                String attrs = VFSUtil.getFileAttributes(file);

                // guard against the shell being closed before this runs
                if (shell.isDisposed())
                    return;
                TableItem tableItem = new TableItem(table, 0);
                tableItem.setText(strings);
                tableItem.setImage(iconImage);
                tableItem.setData(TABLEITEMDATA_FILE, file);
                tableItem.setData(TABLEITEMDATA_CONNECTIONID, connectionId);

                if (attrs.length() > 0)
                    tableItem.setData("TIP_TEXT", attrs);
            }
        });
    }

    /**
     * Resolve a FileSystem Uri seting progress UI messages
     */
    public FileObject resolveURI(String Uri, String connectionId) throws FileSystemException {
        shell.setCursor(CFPluginImages.cursors[CFPluginImages.cursorWait]);
        shell.setText(getResourceString("Title.Resolving", new Object[] { Uri }));
        FTPConnectionProperties connectionProperties = fConnections.get(connectionId);

        FileObject fo = fsManager.resolveFile(Uri, connectionProperties.getFileSystemOptions());

        shell.setCursor(CFPluginImages.cursors[CFPluginImages.cursorDefault]);
        shell.setText(getResourceString("Title", new Object[] { Uri }));

        return fo;
    }

    /**
     * Gets a directory listing
     * 
     * @param file the directory to be listed
     * @return an array of files this directory contains, may be empty but not null
     */
    private FileObject[] getDirectoryList(FileObject file) throws FileSystemException {
        FileObject[] list = file.getChildren();

        if (list == null) {
            return new FileObject[0];
        }

        VFSUtil.sortFiles(list);

        return list;
    }

    /**
     * Delete selected files
     * @param files
     */
    private void deleteObjects(FileObject[] files) {
        // run a delete operation
        try {
            FileOperation delOperation = new FileOperation(fsManager, FileOperation.DELETE, true);
            delOperation.setDeleteArgs(files);

            new ProgressMonitorDialog(shell).run(true, true, delOperation);
        } catch (InvocationTargetException e) {
            VFSUtil.MessageBoxError(e.getMessage());
        } catch (InterruptedException e) {
            VFSUtil.MessageBoxInfo(e.getMessage());
        }

        // Refresh
        doRefresh();
    }

    @Override
    public void setFocus() {
        // TODO Auto-generated method stub

    }

    public boolean show(ShowInContext context) {
        String filePath;

        if (table == null || context == null)
            return false;
        ISelection sel = context.getSelection();
        Object input = context.getInput();
        if (input instanceof CFJavaFileEditorInput) {
            filePath = ((CFJavaFileEditorInput) input).getPath(input).toString();
        } else {
            filePath = ((FileEditorInput) input).getFile().getRawLocation().toString();
        }

        //showFile(filePath);
        return false;
    }

    /**
     * @param currentConnectionId the currentConnectionId to set
     */
    public void setCurrentConnectionId(String currentConnectionId) {
        this.currentConnectionId = currentConnectionId;
    }

    /**
     * @return the currentConnectionId
     */
    public String getCurrentConnectionId() {
        return currentConnectionId;
    }

    /**
     * @param currentDirectory the currentDirectory to set
     */
    public void setCurrentDirectory(FileObject currentDirectory) {
        this.currentDirectory = currentDirectory;
    }

    /**
     * @return the currentDirectory
     */
    public FileObject getCurrentDirectory() {
        return currentDirectory;
    }

    public class FileAndConnectionId {
        public FileObject file = null;
        public String connectionId = "";

        public FileAndConnectionId(FileObject file, String connectionId) {
            super();
            this.file = file;
            this.connectionId = connectionId;
        }

    }
}