com.archivas.clienttools.arcmover.gui.panels.ProfilePanel.java Source code

Java tutorial

Introduction

Here is the source code for com.archivas.clienttools.arcmover.gui.panels.ProfilePanel.java

Source

// Copyright 2007 Hitachi Data Systems
// All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package com.archivas.clienttools.arcmover.gui.panels;

import com.archivas.clienttools.arcmover.gui.*;
import com.archivas.clienttools.arcmover.gui.profileadapter.Hcp3AuthNamepaceProfilePanelAdapter;
import com.archivas.clienttools.arcmover.gui.profileadapter.ProfilePanelAdapter;
import com.archivas.clienttools.arcmover.gui.ssl.SSLCertificateDialog;
import com.archivas.clienttools.arcmover.gui.util.FileListTable;
import com.archivas.clienttools.arcmover.gui.util.FileListTableModel;
import com.archivas.clienttools.arcmover.gui.util.GUIHelper;
import com.archivas.clienttools.arcutils.api.ArcMoverEngine;
import com.archivas.clienttools.arcutils.api.ArcMoverFactory;
import com.archivas.clienttools.arcutils.api.jobs.DeleteJob;
import com.archivas.clienttools.arcutils.api.jobs.SetMetadataJob;
import com.archivas.clienttools.arcutils.config.HCPMoverProperties;
import com.archivas.clienttools.arcutils.impl.adapter.StorageAdapterException;
import com.archivas.clienttools.arcutils.impl.adapter.StorageAdapterLiteralException;
import com.archivas.clienttools.arcutils.profile.*;
import com.archivas.clienttools.arcutils.model.*;
import com.archivas.clienttools.arcutils.utils.FileUtil;
import com.archivas.clienttools.arcutils.utils.database.DBUtils;
import org.apache.http.HttpStatus;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.table.JTableHeader;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ProfilePanel extends JPanel {
    // logging
    private static final Logger LOG = Logger.getLogger(ProfilePanel.class.getName());

    public static final String NEWLINE = System.getProperty("line.separator");
    public static final String VERSION_PREFIX = "Versions for: ";
    public static final int UI_FIRST_BATCH_SIZE = HCPMoverProperties.UI_FIRST_BATCH_SIZE.getAsInt();
    private static final String BROWSE_LFS_TEXT = "Browse Local File System";

    private FileListTableModel fileListModel;
    private FileListTable fileList;
    private JScrollPane fileListScrollPane;
    private JButton refreshButton;
    private JComboBox pathCombo;
    private PathComboActionListener pathComboActionListener;
    private ProfileComboBoxModel profileModel;
    private JComboBox profileSelectionCombo;
    private JButton upDirectoryButton;
    private JButton browseDirectoryButton;
    private JButton sslButton;
    private JButton homeDirectoryButton;

    private ArcMoverEngine arcMoverEngine = ArcMoverFactory.getInstance();
    private ArcMoverDirectory currentDir;
    private ArcMoverFile selectedFile = null;
    private boolean selectionIsReadOnly = false;
    private boolean forceRefresh = false;

    private JPopupMenu rightClickMenu = new JPopupMenu();
    private JMenuItem openMenuItem;
    private JMenuItem deleteMenuItem;
    private JMenuItem setMetadataMenuItem;
    private JMenuItem renameMenuItem;
    private JMenuItem refreshMenuItem;
    private JMenuItem mkdirMenuItem;
    private JMenuItem browseMenuItem;
    private JMenuItem propertiesMenuItem;

    /**
     * Enables/disables all subcomponents of this panel. This also toggles the ability to drag and
     * drop and conditionally freezes the ability to resize the file list column headers.
     *
     * @param enabled
     *            Whether or not this panel is enabled
     */
    private void subSetEnabled(final boolean enabled) {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "subSetEnabled is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        enableDragAndDrop(enabled);
        GUIHelper.enableComponent(this, enabled);
        setEnabled(true); // Re-enable the panel since GUIHelper.enableComponent disables it
        sslButton.setEnabled(enabled && profileModel.getProfileAdapter() != null
                && profileModel.getProfileAdapter().getSSLCertChain() != null);
        browseDirectoryButton.setEnabled(enabled && getSelectedProfile().getType() == ProfileType.FILESYSTEM);
        JTableHeader header = fileList.getTableHeader();
        if (header != null) {
            header.setResizingAllowed(enabled);
        }
    }

    private int lockCount = 0;
    public static String LOCK_PROPERTY = "locked";

    /**
     * Whether or not this profile panel is considered "locked" and is thus in a volatile state. No
     * actions relating to this panel should be attempted so long as it is locked. The ProfilePanel
     * will fire a LOCK_PROPERTY event when the lock status changes.
     * 
     * @return Whether the panel is locked or not
     */
    public boolean isLocked() {
        return lockCount > 0;
    }

    private void lock() {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "lock is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        lockCount++;
        updateLockState();
        if (lockCount == 1) {
            firePropertyChange(LOCK_PROPERTY, false, true);
        }
    }

    private void unlock() {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "unlock is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        lockCount--;
        if (lockCount < 0) {
            String errMsg = "lockCount is less than zero: " + lockCount;
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        updateLockState();
        if (lockCount == 0) {
            firePropertyChange(LOCK_PROPERTY, true, false);
        }
    }

    private void updateLockState() {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "updateLockState is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        boolean locked = isLocked();

        if (locked) {
            setCursor(HCPDataMigrator.waitCursor);
        }
        subSetEnabled(!locked);
        if (!locked) {
            setCursor(null);
        }
    }

    public ProfilePanel() {
        initGuiComponents();

        initializeMouseListener();
        initializeActionListeners();
        initializeAddressBar();
        enableDragAndDrop(true);
        initializeRightClickMenu();
    }

    private void initGuiComponents() {
        fileListModel = new FileListTableModel();
        fileList = new FileListTable(fileListModel);
        fileList.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    openSelectedFile();
                    e.consume();
                }
            }
        });
        fileList.setAutoCreateColumnsFromModel(true);
        fileList.setDropMode(DropMode.ON_OR_INSERT_ROWS);
        fileList.setFillsViewportHeight(true);
        fileList.setGridColor(new Color(-1));

        fileListScrollPane = new JScrollPane(fileList);
        fileListScrollPane.setAutoscrolls(false);
        fileListScrollPane.setBackground(UIManager.getColor("TableHeader.background"));
        fileListScrollPane.setPreferredSize(new Dimension(100, 128));
        fileListScrollPane.setEnabled(false);

        //
        // toolbar
        //

        final JToolBar toolBar1 = new JToolBar();
        toolBar1.setBorderPainted(false);
        toolBar1.setFloatable(false);
        toolBar1.setRollover(true);
        toolBar1.putClientProperty("JToolBar.isRollover", Boolean.TRUE);

        homeDirectoryButton = new JButton();
        homeDirectoryButton.setHorizontalAlignment(2);
        homeDirectoryButton.setIcon(GUIHelper.HOME_ICON);
        homeDirectoryButton.setMargin(new Insets(3, 3, 3, 3));
        homeDirectoryButton.setText("");
        homeDirectoryButton.setToolTipText("Go to home directory");
        homeDirectoryButton.setEnabled(false);
        toolBar1.add(homeDirectoryButton);

        refreshButton = new JButton();
        refreshButton.setHorizontalAlignment(2);
        refreshButton.setIcon(new ImageIcon(getClass().getResource("/images/refresh.gif")));
        refreshButton.setMargin(new Insets(3, 3, 3, 3));
        refreshButton.setText("");
        refreshButton.setToolTipText("Refresh current directory listing");
        refreshButton.setEnabled(false);
        toolBar1.add(refreshButton);

        upDirectoryButton = new JButton();
        upDirectoryButton.setHideActionText(false);
        upDirectoryButton.setHorizontalAlignment(2);
        upDirectoryButton.setIcon(GUIHelper.UP_DIR_ICON);
        upDirectoryButton.setMargin(new Insets(3, 3, 3, 3));
        upDirectoryButton.setToolTipText("Up");
        upDirectoryButton.setEnabled(false);
        toolBar1.add(upDirectoryButton);

        browseDirectoryButton = new JButton();
        browseDirectoryButton.setHideActionText(false);
        browseDirectoryButton.setHorizontalAlignment(2);
        browseDirectoryButton.setIcon(GUIHelper.DIRECTORY_ICON);
        browseDirectoryButton.setMargin(new Insets(3, 3, 3, 3));
        browseDirectoryButton.setToolTipText(BROWSE_LFS_TEXT);
        browseDirectoryButton.setEnabled(false);
        toolBar1.add(browseDirectoryButton);

        profileModel = new ProfileComboBoxModel();
        profileSelectionCombo = new JComboBox(profileModel);
        profileSelectionCombo.setEnabled(false);
        profileSelectionCombo.setToolTipText("Select a namespace profile");
        profileSelectionCombo.setPrototypeDisplayValue("#");

        pathCombo = new JComboBox();
        pathCombo.setEditable(false);
        pathCombo.setEnabled(false);
        pathCombo.setToolTipText("Current directory path");
        pathCombo.setPrototypeDisplayValue("#");

        sslButton = new JButton();
        sslButton.setAlignmentY(0.0f);
        sslButton.setBorderPainted(false);
        sslButton.setHorizontalAlignment(2);
        sslButton.setHorizontalTextPosition(11);
        sslButton.setIcon(new ImageIcon(getClass().getResource("/images/lockedstate.gif")));
        sslButton.setMargin(new Insets(0, 0, 0, 0));
        sslButton.setMaximumSize(new Dimension(20, 20));
        sslButton.setMinimumSize(new Dimension(20, 20));
        sslButton.setPreferredSize(new Dimension(20, 20));
        sslButton.setText("");
        sslButton.setToolTipText("View certificate");
        sslButton.setEnabled(false);

        //
        // profile and toolbar buttons
        //
        JPanel profileAndToolbarPanel = new FixedHeightPanel();
        profileAndToolbarPanel.setLayout(new BoxLayout(profileAndToolbarPanel, BoxLayout.X_AXIS));
        profileAndToolbarPanel.add(profileSelectionCombo);
        profileAndToolbarPanel.add(Box.createHorizontalStrut(25));
        profileAndToolbarPanel.add(toolBar1);

        //
        // Path & SSLCert button
        //
        JPanel pathPanel = new FixedHeightPanel();
        pathPanel.setLayout(new BoxLayout(pathPanel, BoxLayout.X_AXIS));
        pathCombo.setAlignmentY(CENTER_ALIGNMENT);
        pathPanel.add(pathCombo);
        pathPanel.add(Box.createHorizontalStrut(5));
        sslButton.setAlignmentY(CENTER_ALIGNMENT);
        pathPanel.add(sslButton);

        //
        // Put it all together
        //
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        add(profileAndToolbarPanel);
        add(Box.createVerticalStrut(5));
        add(pathPanel);
        add(Box.createVerticalStrut(5));
        add(fileListScrollPane);
        setBorder(new EmptyBorder(12, 12, 12, 12));
    }

    public void setFileFont(Font newFont) {
        pathCombo.setFont(newFont);
        fileList.setFileFont(newFont);
        repaint();
    }

    public Font getFileFont() {
        return fileList.getFont();
    }

    public void enableDragAndDrop(boolean flag) {
        flag = flag && !isSelectionReadOnly(); // Can't drag-and-drop into a read-only
        MoverTransferHandler moveTransferHandler = new MoverTransferHandler(this);
        fileList.setTransferHandler(moveTransferHandler);
        fileList.setDragEnabled(true);
        fileList.setDropMode(DropMode.INSERT_ROWS);
        moveTransferHandler.setDropEnabled(flag);
    }

    private void initializeMouseListener() {

        fileList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                onFileListSelectionChangedAction(e);
            }
        });

        fileList.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                onFileListMouseClicked(e);
            }

            public void mouseReleased(MouseEvent e) {
                onFileListMouseClicked(e);
            }

            public void mouseClicked(MouseEvent e) {
                // check if the user has double clicked on an item
                if (e.getClickCount() == 2) {
                    onFileListMouseDoubleClicked();
                }
            }
        });
    }

    private void updateSelectedFile() {
        int selection = fileList.getSelectedRow();
        if (selection >= 0) {
            selectedFile = fileListModel.getFileAt(selection);
        } else {
            selectedFile = null;
        }
    }

    //
    // process new user selection in the local dir list
    //
    private void onFileListSelectionChangedAction(ListSelectionEvent evt) {
        try {
            updateSelectedFile();
            // Reset selections based on this list change
            if (selectedFile != null) {
                HCPDataMigrator.getInstance().selectionFromPanel(this);
            }
            HCPDataMigrator.getInstance().updateLockable();
            updateRightClickMenu();
        } catch (Exception e) {
            String msg = DBUtils.getErrorMessage("An error occurred selecting file", e);
            LOG.log(Level.WARNING, msg, e);
            GUIHelper.showMessageDialog(this, msg, "Error Selecting File", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * Called one time at window creation time
     */
    private void initializeRightClickMenu() {
        openMenuItem = new JMenuItem("Open");
        openMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    setCursor(HCPDataMigrator.waitCursor);
                    openSelectedRow();
                } finally {
                    setCursor(null);
                }
            }
        });
        rightClickMenu.add(openMenuItem);
        rightClickMenu.addSeparator();

        deleteMenuItem = new JMenuItem("Delete");
        deleteMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    setCursor(HCPDataMigrator.waitCursor);
                    deleteSelectedRows();
                } finally {
                    setCursor(null);
                }
            }
        });
        rightClickMenu.add(deleteMenuItem);

        setMetadataMenuItem = new JMenuItem("Set Metadata");
        setMetadataMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    setCursor(HCPDataMigrator.waitCursor);
                    setMetadataOnSelectedRows();
                } finally {
                    setCursor(null);
                }
            }
        });
        rightClickMenu.add(setMetadataMenuItem);

        renameMenuItem = new JMenuItem("Rename");
        renameMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    setCursor(HCPDataMigrator.waitCursor);
                    renameSelectedRow();
                } finally {
                    setCursor(null);
                }
            }
        });
        rightClickMenu.add(renameMenuItem);
        rightClickMenu.addSeparator();

        refreshMenuItem = new JMenuItem("Refresh");
        refreshMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                refreshPathAndFileList();
            }
        });
        rightClickMenu.add(refreshMenuItem);

        mkdirMenuItem = new JMenuItem("New Directory");
        mkdirMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                mkdirAction();
            }
        });
        rightClickMenu.add(mkdirMenuItem);

        browseMenuItem = new JMenuItem(BROWSE_LFS_TEXT);
        browseMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setPathToBrowseDirectory();
            }
        });
        rightClickMenu.add(browseMenuItem);

        rightClickMenu.addSeparator();

        propertiesMenuItem = new JMenuItem("Properties");
        propertiesMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    setCursor(HCPDataMigrator.waitCursor);
                    displaySelectedObjectProperties();
                } finally {
                    setCursor(null);
                }
            }
        });
        rightClickMenu.add(propertiesMenuItem);

    }

    public boolean supportsOpenAction(ArcMoverFile file, boolean single) {
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        return supportsOpenAction(file, single, profileAdapter);
    }

    public boolean supportsDeleteAction(ArcMoverFile file, boolean single) {
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        return supportsDeleteAction(file, single, profileAdapter);
    }

    public boolean supportsSetMetadataAction(ArcMoverFile file, boolean single) {
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        return supportsSetMetadataAction(file, single, profileAdapter);
    }

    public boolean supportsRenameAction(ArcMoverFile file, boolean single) {
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        return supportsRenameAction(file, single, profileAdapter);
    }

    public boolean supportsRefreshAction(ArcMoverFile file, boolean single) {
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        return supportsRefreshAction(file, single, profileAdapter);
    }

    public boolean supportsMkdirAction(ArcMoverFile file, boolean single) {
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        return supportsMkdirAction(file, single, profileAdapter);
    }

    public boolean supportsPropertiesAction(ArcMoverFile file, boolean single) {
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        return supportsPropertiesAction(file, single, profileAdapter);
    }

    private boolean supportsOpenAction(ArcMoverFile file, boolean single, ProfilePanelAdapter profileAdapter) {
        return profileAdapter != null && profileAdapter.supportsOpenAction(file, single);
    }

    private boolean supportsDeleteAction(ArcMoverFile file, boolean single, ProfilePanelAdapter profileAdapter) {
        return profileAdapter != null && profileAdapter.supportsDeleteAction(file, single);
    }

    private boolean supportsSetMetadataAction(ArcMoverFile file, boolean single,
            ProfilePanelAdapter profileAdapter) {
        return profileAdapter != null && profileAdapter.supportsSetMetadataAction(file, single);
    }

    private boolean supportsRenameAction(ArcMoverFile file, boolean single, ProfilePanelAdapter profileAdapter) {
        return profileAdapter != null && profileAdapter.supportsRenameAction(file, single);
    }

    private boolean supportsRefreshAction(ArcMoverFile file, boolean single, ProfilePanelAdapter profileAdapter) {
        return profileAdapter != null && profileAdapter.supportsRefreshAction(file, single);
    }

    private boolean supportsMkdirAction(ArcMoverFile file, boolean single, ProfilePanelAdapter profileAdapter) {
        return profileAdapter != null && profileAdapter.supportsMkdirAction(file, single);
    }

    private boolean supportsPropertiesAction(ArcMoverFile file, boolean single,
            ProfilePanelAdapter profileAdapter) {
        return profileAdapter != null && profileAdapter.supportsPropertiesAction(file, single);
    }

    private void updateRightClickMenu() {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "updateRightClickMenu is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        if (profileAdapter != null) {
            boolean single = (getSelectedRowCount() == 1);
            openMenuItem.setEnabled(supportsOpenAction(selectedFile, single, profileAdapter));

            boolean showDelete = !currentDir.isVersionList()
                    && supportsDeleteAction(selectedFile, single, profileAdapter) && !JobDialog.isOpen(); // Can't start a delete job when a job already exists
            deleteMenuItem.setEnabled(showDelete);

            boolean showSetMetadata = !currentDir.isVersionList()
                    && supportsSetMetadataAction(selectedFile, single, profileAdapter) && !JobDialog.isOpen(); // Can't start a set metadata job when a job already
                                                                                                                                                                     // exists
            setMetadataMenuItem.setEnabled(showSetMetadata);

            boolean showRename = supportsRenameAction(selectedFile, single, profileAdapter) && !JobDialog.isOpen();
            renameMenuItem.setEnabled(showRename);

            refreshMenuItem.setEnabled(supportsRefreshAction(selectedFile, single, profileAdapter));

            boolean showMkdir = !currentDir.isVersionList()
                    && supportsMkdirAction(selectedFile, single, profileAdapter);
            mkdirMenuItem.setEnabled(showMkdir);

            propertiesMenuItem.setEnabled(supportsPropertiesAction(selectedFile, single, profileAdapter));
        }
        browseMenuItem.setEnabled(FileSystemProfile.LOCAL_FILESYSTEM_PROFILE == getSelectedProfile());
    }

    private void initializeActionListeners() {
        //
        // initialize local navigation buttons
        //
        upDirectoryButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setPathAndFileListUpDirectory();
            }
        });

        homeDirectoryButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setPathAndFileListToHomeDirectory();
            }
        });

        refreshButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                refreshPathAndFileList();
            }
        });

        browseDirectoryButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setPathToBrowseDirectory();
            }
        });

        profileSelectionCombo.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onProfileSelected();
            }
        });

        sslButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    setCursor(HCPDataMigrator.waitCursor);
                    showSSLCert();
                } finally {
                    setCursor(null);
                }
            }
        });
    }

    private String browseDirectory() {
        if (getSelectedProfile() != FileSystemProfile.LOCAL_FILESYSTEM_PROFILE) {
            // browse only supported on LFS
            return null;
        }

        String loadPath = currentDir.getPath();
        JFileChooser chooser = new JFileChooser(loadPath);
        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

        int retVal = chooser.showOpenDialog(this);
        if (retVal == JFileChooser.APPROVE_OPTION) {
            return chooser.getSelectedFile().getAbsolutePath();
        } else {
            return null;
        }
    }

    private void setPathToBrowseDirectory() {
        try {
            String path = browseDirectory();
            if (path != null) {
                setPathAndFileListToDirectory(path);
            }
        } catch (Exception e) {
            String msg = DBUtils.getErrorMessage("An error occurred selecting a directory", e);
            LOG.warning(msg);
            GUIHelper.showMessageDialog(this, msg, "Error Selecting Directory", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * The basic algorithm here is: Lock the panel In an asynchronous worker thread: Try to get
     * directory for last history item If error try to get directory for home If error back out
     * reset to original settings If success update current state Unlock the panel on completion of
     * the worker thread
     */
    private void onProfileSelected() {
        lock();
        new SetProfileWorker().execute();
    }

    class SetProfileWorker extends SwingWorker {
        @Override
        protected Object doInBackground() throws Exception {
            try {
                setProfile();
            } catch (Exception e) {
                LOG.log(Level.WARNING, "Error during setProfile", e);
            }
            return null;
        }

        @Override
        protected void done() {
            unlock();
        }
    }

    private void setProfile() {
        AbstractProfileBase profile = profileModel.getSelectedProfile();
        ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
        String[] paths = HistoryManager.openHistory(profile);
        try {
            if (!ArcMoverFactory.getInstance().testConnection(profile)) {
                throw new IllegalStateException(String.format("Cannot connect to profile %s.", profile.getName()));
            }

            // If we have no history then pretend we only had the home directory
            if (paths.length == 0) {
                paths = new String[] { profileAdapter.getHomeDirectory().getPath() };
            }

            String path = paths[0];
            boolean tryHome = false;
            ArcMoverDirectory dir = null;
            try {
                List<String> fileListColumns = profileAdapter.getFileListColumns();

                path = stripVersioningIfUnsupported(path);
                boolean isVersioned = isVersioned(path);

                if (isVersioned) {
                    dir = profileAdapter.getVersions(filePath(path));
                    fileListColumns = ((Hcp3AuthNamepaceProfilePanelAdapter) profileAdapter)
                            .getVersionListColumns();
                } else {
                    dir = profileAdapter.getDirectory(path);
                }
                setFileListToDirectory(dir, fileListColumns);
            } catch (Exception e) {
                LOG.log(Level.WARNING,
                        "An error occurred opening directory " + path + " on profile " + profile.getName(), e);

                Throwable rootCause = e;
                while (rootCause.getCause() != null) {
                    rootCause = rootCause.getCause();
                }

                if (rootCause instanceof SSLPeerUnverifiedException) {
                    throw e; // Don't bother with the home directory, user canceled the SSL
                             // verification
                }

                tryHome = true;
            }
            if (tryHome) {
                // Try home directory instead
                String badPath = path;
                dir = profileAdapter.getHomeDirectory();
                path = dir.getPath();
                // We know home is not versioned so we can use the standard file list columns
                setFileListToDirectory(dir, profileAdapter.getFileListColumns());
                // Home succeeded, so remove the bad path
                HistoryManager.removeFromHistory(profile, badPath);
                paths = HistoryManager.addToHistory(profile, path);
            }
            // success:
            setCurrent(dir, path, paths, profile);
            profileSelectionCombo.setToolTipText(profile.getName());
            HCPDataMigrator.getInstance().saveSelectedProfile(this, profile.getName());
        } catch (Exception e) {
            String msg = "An error occurred opening profile " + profile.getName();
            Throwable rootCause = e;
            while (rootCause.getCause() != null) {
                rootCause = rootCause.getCause();
            }
            if (rootCause instanceof StorageAdapterLiteralException) {
                Integer statusCode = ((StorageAdapterLiteralException) rootCause).getStatusCode();
                if (statusCode != null && statusCode == HttpStatus.SC_FORBIDDEN) { // 403
                    switch (profile.getType()) {
                    case HCP60:
                    case HCP50:
                    case HCP:
                        msg = msg + ": " + "Access denied, please check that the proper protocols are enabled on"
                                + " the namespace and that the user has the correct permissions";
                        break;
                    case HCAP2:
                    case HCP_DEFAULT:
                        msg = msg + ": " + "Access denied, please check that the proper protocols are enabled";
                        break;
                    default:
                        msg = msg + ": " + rootCause.getMessage();
                        break;
                    }
                } else {
                    msg = msg + ": " + rootCause.getMessage();
                }
            } else if (rootCause instanceof SSLPeerUnverifiedException) {
                msg = String.format("SSL certificate is not trusted for profile %s.", profile.getName());
            }
            AbstractProfileBase currentProfile = pathComboActionListener.getCurrentProfile();
            // If it's null or isn't a change, set it to the local filesystem profile
            if (currentProfile == null || currentProfile == profile) {
                currentProfile = FileSystemProfile.LOCAL_FILESYSTEM_PROFILE;
            }
            final AbstractProfileBase currentProfileR = currentProfile;
            GUIHelper.invokeAndWait(new Runnable() {
                public void run() {
                    profileSelectionCombo.setSelectedItem(currentProfileR);
                }
            }, "reset the current profile after an error");

            GUIHelper.showMessageDialog(this, msg, "Error Opening Profile", JOptionPane.ERROR_MESSAGE);
            LOG.log(Level.SEVERE, msg, e);
        }

    }

    /**
     * This updates the current state, updating tooltips, calling to update the file menu and right
     * click menus. Works on the EDT synchronously.
     * 
     * @param dir
     *            The new current directory
     * @param path
     *            The new current path
     * @param profile
     *            The new current profile
     */
    private void setCurrent(final ArcMoverDirectory dir, final String path, final String[] paths,
            final AbstractProfileBase profile) {
        if (SwingUtilities.isEventDispatchThread()) {
            String errMsg = "setCurrent is on the EDT but it should not be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        GUIHelper.invokeAndWait(new Runnable() {
            public void run() {
                setCurrentAux(dir, path, paths, profile);
            }
        }, "set current state");
    }

    private void setCurrentAux(ArcMoverDirectory dir, String path, String[] paths, AbstractProfileBase profile) {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "setCurrentAux is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        // if this is LFS, we want a browse option at the top
        if (getSelectedProfile() == FileSystemProfile.LOCAL_FILESYSTEM_PROFILE) {
            String[] pathsWithBrowse = new String[paths.length + 1];
            pathsWithBrowse[0] = BROWSE_LFS_TEXT;
            System.arraycopy(paths, 0, pathsWithBrowse, 1, paths.length);
            paths = pathsWithBrowse;
        }

        pathCombo.setModel(new DefaultComboBoxModel(paths));
        pathCombo.setEditable(true);
        currentDir = dir;
        pathComboActionListener.setCurrentPath(path);
        pathComboActionListener.setCurrentProfile(profile);
        profile.setDisplayPath(path);
        forceRefresh = false;
        pathCombo.setSelectedItem(path);
        pathCombo.setToolTipText(profile.decode(path));
        HCPDataMigrator.getInstance().updateLockable();
        updateRightClickMenu();
    }

    private void setPathAndFileListUpDirectory() {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "setPathAndFileListUpDirectory is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        try {
            ArcMoverDirectory parent = currentDir.getParent();
            if (parent != null) {
                setPathAndFileListToDirectory(parent.getPath());
            }
        } catch (Exception e1) {
            JOptionPane.showMessageDialog(this,
                    "An error occurred opening directory.  See logs for more information.",
                    "Error Opening Directory", JOptionPane.ERROR_MESSAGE);
            LOG.log(Level.WARNING, "An error occurred getting a directory listing: " + e1.toString(), e1);
        }
    }

    private void setPathAndFileListToHomeDirectory() {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "setPathAndFileListToHomeDirectory is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        try {
            ProfilePanelAdapter profileAdapter = profileModel.getProfileAdapter();
            setPathAndFileListToDirectory(profileAdapter.getHomeDirectory().getPath());
        } catch (Exception e) {
            String errMsg = DBUtils.getErrorMessage("An error occurred opening home directory", e);
            GUIHelper.showMessageDialog(this, errMsg, "Error Opening Directory", JOptionPane.ERROR_MESSAGE);
            LOG.log(Level.WARNING, "An error occurred getting the home directory listing: " + e.toString(), e);
        }
    }

    public void refreshPathAndFileList() {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "refreshPathAndFileList is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        try {
            forceRefresh = true;
            if (currentDir.isVersionList()) {
                setPathAndFileListToDirectory(versionPath(currentDir));
            } else {
                setPathAndFileListToDirectory(currentDir.getPath());
            }
        } catch (Exception e1) {
            JOptionPane.showMessageDialog(this,
                    "An error occurred refreshing the directory listing.  See logs for more information.",
                    "Error Reloading Directory", JOptionPane.ERROR_MESSAGE);
            LOG.log(Level.WARNING, "An error occurred refreshing a directory listing: " + e1.toString(), e1);
        }
    }

    /**
     * Updates the path combo box and the file list to reflect the specified directory
     *
     * @param path
     *            -
     * @throws Exception
     */
    private void setPathAndFileListToDirectory(String path) {
        // pathCombo.setSelectedItem() causes the action listener for the pathCombo to be fired,
        // which will update the file list
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "setPathAndFileListToDirectory is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        pathCombo.setSelectedItem(path);
    }

    /**
     * This sets the file list for this panel to the files in the given directory. Can throw a
     * StorageAdapterException when getting the file list for the directory. The actual work on the
     * fileList is completed on the EDT for thread safety.
     * 
     * @param dir
     *            The directory to display
     * @param colsToDisplay
     *            Columns to display in the file list
     * @throws Exception
     *             An exception thrown by dir.getFileList or SwingUtilities.invokeAndWait
     */
    private void setFileListToDirectory(ArcMoverDirectory dir, List<String> colsToDisplay) throws Exception {
        // Should not be run on the EDT
        if (SwingUtilities.isEventDispatchThread()) {
            String errMsg = "setFileListToDirectory is on the EDT but it should not be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        List<ArcMoverFile> files = dir.getFileList(UI_FIRST_BATCH_SIZE);
        int badElementCnt = dir.getFileListBadElementCnt();

        List<ArcMoverFile> sortedDirs = new ArrayList<ArcMoverFile>();
        List<ArcMoverFile> sortedFiles = new ArrayList<ArcMoverFile>();
        List<ArcMoverFile> sortedSymlinks = new ArrayList<ArcMoverFile>();
        ArcMoverFile mostRecentVersion = null;
        boolean isVersionList = dir.isVersionList();

        for (ArcMoverFile f : files) {
            // if f is a directory or is a link to a directory, show as directory
            if (f.isDirectory()) {
                sortedDirs.add(f);
            } else if (f.isSymlink()) {
                sortedSymlinks.add(f);
            } else {
                if (mostRecentVersion == null) {
                    if (f.getMetadata().hasVersionNumber()) {
                        mostRecentVersion = f;
                    }
                } else {
                    if (isVersionList && f.getMetadata().hasVersionNumber()) {
                        mostRecentVersion.setLatestVersion(false);

                        if (f.getMetadata().getVersionNumber() > mostRecentVersion.getMetadata()
                                .getVersionNumber()) {
                            mostRecentVersion = f;
                        }
                    }
                }

                sortedFiles.add(f);
            }
        }

        if (mostRecentVersion != null) {
            mostRecentVersion.setLatestVersion(true);
        }

        Collections.sort(sortedDirs);
        Collections.sort(sortedSymlinks);

        if (isVersionList) {
            Collections.sort(sortedFiles, ArcMoverVersionedFileComparator.getInstance());
        } else {
            Collections.sort(sortedFiles, ArcMoverFileDefaultComparator.getInstance());
        }

        files.clear();
        files.addAll(sortedDirs);
        files.addAll(sortedSymlinks);
        files.addAll(sortedFiles);

        GUIHelper.invokeAndWait(
                getFileListRunner(colsToDisplay, files, isVersionList, badElementCnt, dir.getPath()),
                "get file list");
    }

    private void setFileList(final List<String> colsToDisplay, final List<ArcMoverFile> files,
            final boolean isVersionList, int badElementCnt, String dirPath) {
        fileListModel = new FileListTableModel(new ArrayList<String>(colsToDisplay));
        fileList.setModel(fileListModel);
        fileList.setColumnCellRenderers();
        fileList.setListData(new ArrayList<ArcMoverFile>(files));
        if (isVersionList) {
            fileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        } else {
            fileList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
        }
        if (badElementCnt > 0) {
            String errMsg = String.format("Warning: omitted %d unsupported filename(s) in directory listing for %s",
                    badElementCnt, profileModel.getSelectedProfile().decode(dirPath));
            LOG.log(Level.WARNING, errMsg);
            if (HCPMoverProperties.DISPLAY_WARNINGS_FOR_BAD_ELEMENTS_IN_DIRECTORY_LISTINGS.getAsBoolean()) {
                GUIHelper.showMessageDialog(this, errMsg, "", JOptionPane.WARNING_MESSAGE);
            }
        }
    }

    private Runnable getFileListRunner(final List<String> colsToDisplay, final List<ArcMoverFile> files,
            final boolean isVersionList, final int badElementCnt, final String dirPath) {
        return new Runnable() {
            public void run() {
                setFileList(colsToDisplay, files, isVersionList, badElementCnt, dirPath);
            }
        };
    }

    protected void initializeAddressBar() {
        pathComboActionListener = new PathComboActionListener();
        pathCombo.addActionListener(pathComboActionListener);
        pathCombo.setRenderer(new PathComboBoxRenderer());
        pathCombo.setEditor(new PathComboBoxEditor());

        /*
         * // call onCancel() on ESCAPE pathCombo.registerKeyboardAction(new ActionListener() {
         * public void actionPerformed(ActionEvent e) { if (fileList.isEnabled()) { try {
         * pathCombo.setModel(new
         * DefaultComboBoxModel(HistoryManager.openHistory(getMoverProfile())));
         * pathCombo.setPopupVisible(false); } catch (Exception e1) { String msg =
         * "Unexpected Error"; LOG.log(Level.WARNING, msg, e1);
         * GUIHelper.showMessageDialog(ProfilePanel.this, msg, "Error", JOptionPane.ERROR_MESSAGE);
         * } } } }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
         * JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
         * 
         * pathCombo.setModel(new
         * DefaultComboBoxModel(HistoryManager.openHistory(getMoverProfile())));
         * 
         * pathCombo.setPrototypeDisplayValue("#");
         */
    }

    public void refreshProfiles() {
        profileModel.loadProfiles();
    }

    public void setSelectedProfile(AbstractProfileBase profile) {
        profileSelectionCombo.setSelectedItem(profile);
    }

    public AbstractProfileBase getSelectedProfile() {
        return profileModel.getSelectedProfile();
    }

    public ArcMoverDirectory getCurrentDirectory() {
        return currentDir;
    }

    // for drag & drop support, we need to know what file is at a particular row
    public ArcMoverFile getFileAt(int row) {
        ArcMoverFile moverFile = null;
        try {
            moverFile = fileListModel.getFileAt(row);
        } catch (IndexOutOfBoundsException e) {
            return null;
        }

        return moverFile;
    }

    public void resizeFileList() {
        GUIHelper.autoResizeColWidth(fileList);
    }

    public void clearSelection() {
        fileList.clearSelection();
    }

    /**
     * Wrapper to return the selected files from the file list model.
     */
    public ArcMoverFile[] getSelectedFiles() {
        int[] rows = fileList.getSelectedRows();
        ArcMoverFile[] files = new ArcMoverFile[rows.length];
        for (int i = 0; i < rows.length; i++) {
            files[i] = fileListModel.getFileAt(rows[i]);
        }
        return files;
    }

    /**
     * Gets the number of selected, possibly-deleted files
     * 
     * @return the number of selected, possibly-deleted files
     */
    public int getSelectedRowCount() {
        return fileList.getSelectedRowCount();
    }

    /**
     * Gets the number of selected, non-deleted files
     * 
     * @return the number of selected, non-deleted files
     */
    public int getNonDeletedSelectedFileCount() {
        int count = 0;
        for (int i : fileList.getSelectedRows()) {
            ArcMoverFile file = fileListModel.getFileAt(i);
            if (!file.isDeleted()) {
                count++;
            }
        }
        return count;
    }

    private void onFileListMouseClicked(MouseEvent evt) {
        try {
            if (evt.isPopupTrigger()) {

                updateSelectedFile();
                updateRightClickMenu();
                rightClickMenu.show(fileList, evt.getX(), evt.getY());
            }
        } catch (Exception e1) {
            String msg = "Unexpected error showing right-click menu";
            LOG.log(Level.WARNING, msg, e1);
            JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE);
        }
    }

    protected void openSelectedFile() {
        if (!SwingUtilities.isEventDispatchThread()) {
            String errMsg = "openSelectedFile is not on the EDT but it should be";
            IllegalStateException ex = new IllegalStateException(errMsg);
            LOG.log(Level.SEVERE, errMsg, ex);
            throw ex;
        }
        try {
            if (selectedFile != null) {
                // check if this item is a directory
                if (selectedFile.isDirectory()) {
                    ArcMoverDirectory newDir = (ArcMoverDirectory) selectedFile;
                    if (!newDir.canRead()) {
                        GUIHelper.showMessageDialog(this,
                                "An error occurred reading directory "
                                        + profileModel.getSelectedProfile().decode(newDir.getPath()),
                                "Error Reading Directory", JOptionPane.ERROR_MESSAGE);
                    } else {
                        setPathAndFileListToDirectory(newDir.getPath());
                    }
                } else if (selectedFile.isSymlink()) {
                    // do nothing
                } else if (!selectedFile.getParent().isVersionList()
                        && profileModel.getSelectedProfile().supportsVersioning()) {
                    // This is file with versions, and we're not in the version list.
                    setPathAndFileListToDirectory(versionPath(selectedFile));
                } else {
                    openSelectedRow();
                }
            }
        } catch (Exception e) {
            String msg = "Unexpected error";
            LOG.log(Level.WARNING, msg, e);
            JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE);
        }
    }

    protected void onFileListMouseDoubleClicked() {
        updateSelectedFile();
        openSelectedFile();
        // The right click menu can be different based on whether you went into a versions listing
        // or not
        updateRightClickMenu();
    }

    private String filePath(String versionPath) {
        return versionPath.substring(VERSION_PREFIX.length());
    }

    private String versionPath(ArcMoverFile file) {
        return VERSION_PREFIX + file.getPath();
    }

    /**
     */
    public final void openSelectedRow() {
        if (getSelectedRowCount() != 1) {
            JOptionPane.showMessageDialog(this, "You may only open one object at a time", "",
                    JOptionPane.INFORMATION_MESSAGE);
        }

        if (!selectedFile.isDirectory()) {
            OpenFileThread openFileThread = new OpenFileThread(selectedFile, profileModel.getProfileAdapter());
            openFileThread.execute();
        }
    }

    public final void renameSelectedRow() {
        try {
            if (getSelectedRowCount() != 1) {
                JOptionPane.showMessageDialog(this, "You may only rename one object at a time", "",
                        JOptionPane.INFORMATION_MESSAGE);
                return;
            }

            // show rename dialog
            String newFileName;
            String oldFileName = selectedFile.getFileName();

            // The slugs are stand-ins for file type in messages
            final String lSlug = (selectedFile.isDirectory() ? "directory" : "file");
            final String uSlug = (selectedFile.isDirectory() ? "Directory" : "File");

            do {
                final String message = String.format("New %s name", lSlug);
                final String title = String.format("Rename %s", uSlug);

                // get new file name from user
                newFileName = (String) JOptionPane.showInputDialog(this.getParent(), message, title,
                        JOptionPane.QUESTION_MESSAGE, null, null, selectedFile.getFileName());

                // check if user pressed 'cancel' button
                if (newFileName == null) {
                    LOG.info("Rename operation canceled");

                    return;
                }

                // validate the new file name
                // TODO: Complete validation based on target OS is not performed at this time. If
                // needed, that should be added here.
                if (newFileName.contains("/") || newFileName.contains("\\")) {
                    final String errorMessage = String.format("Invalid %s name", lSlug);
                    JOptionPane.showMessageDialog(this.getParent(), errorMessage, "Rename",
                            JOptionPane.WARNING_MESSAGE);
                    newFileName = null;
                }

            } while ((newFileName == null) || (newFileName.length() <= 0));

            try {
                boolean success;
                success = arcMoverEngine.renameTo(profileModel.getSelectedProfile(),
                        selectedFile.getParent().getPath(), oldFileName, newFileName);
                if (!success) {
                    throw new IOException(String.format("Could not rename %s.", lSlug));
                }
                LOG.info("Renamed " + oldFileName + " to " + newFileName);

                HCPDataMigrator.getInstance().refreshMatchingPanels(profileModel.getSelectedProfile(), currentDir);

            } catch (Exception e1) {
                String uiErrorMsg;
                String logErrorMsg;

                logErrorMsg = "Could not rename " + selectedFile.getPath() + " to " + newFileName;
                uiErrorMsg = DBUtils.getErrorMessage("Error renaming " + selectedFile.getPath(), e1);
                LOG.log(Level.WARNING, logErrorMsg, e1);
                GUIHelper.showMessageDialog(this.getParent(), uiErrorMsg, "Rename", JOptionPane.ERROR_MESSAGE);
            }
        } catch (RuntimeException e1) {
            // TODO: Implement Proper Error Handling
            LOG.log(Level.WARNING, "Unexpected Exception", e1);
            throw e1;
        }
    }

    public final void deleteSelectedRows() {
        try {
            ArcMoverFile[] selectedFiles = getSelectedFiles();
            if (selectedFiles.length > 0) {

                List<ArcDeleteFile> fileNameList = new ArrayList<ArcDeleteFile>();
                // add each selected file to the list
                for (ArcMoverFile nextFile : selectedFiles) {
                    ArcDeleteFile nextDeleteFile = new ArcDeleteFile(nextFile);
                    fileNameList.add(nextDeleteFile);
                }

                DeleteJob job = new DeleteJob(getSelectedProfile(), fileNameList);
                job.setSourcePath(getCurrentDirectory().getPath());

                // won't return until the job window is closed
                JobDialog.openNewJob(job);

                refreshPathAndFileList();
            }
        } catch (JobAlreadyOpenException jaoe) {
            JOptionPane.showMessageDialog(this, "Another job is already open.\nOnly one job may be open at a time.",
                    "Error", JOptionPane.ERROR_MESSAGE);
            JobDialog.showJob();
        } catch (Exception e) {
            String msg = DBUtils.getErrorMessage("An error occurred deleting selected items", e);
            LOG.log(Level.WARNING, msg, e);
            GUIHelper.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE);
        }
    }

    public final void setMetadataOnSelectedRows() {
        try {
            ArcMoverFile[] selectedFiles = getSelectedFiles();
            if (selectedFiles.length > 0) {

                List<ArcSetMetadataFile> fileNameList = new ArrayList<ArcSetMetadataFile>();
                // add each selected file to the list
                for (ArcMoverFile nextFile : selectedFiles) {
                    ArcSetMetadataFile nextMdFile = new ArcSetMetadataFile(nextFile);
                    fileNameList.add(nextMdFile);
                }

                SetMetadataJob job = new SetMetadataJob(getSelectedProfile(), fileNameList);
                job.setSourcePath(getCurrentDirectory().getPath());

                // won't return until the job window is closed
                JobDialog.openNewJob(job);

                refreshPathAndFileList();
            }
        } catch (JobAlreadyOpenException jaoe) {
            JOptionPane.showMessageDialog(this, "Another job is already open.\nOnly one job may be open at a time.",
                    "Error", JOptionPane.ERROR_MESSAGE);
            JobDialog.showJob();
        } catch (Exception e) {
            String msg = DBUtils.getErrorMessage("An error occurred setting metadata on selected items", e);
            LOG.log(Level.WARNING, msg, e);
            GUIHelper.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE);
        }
    }

    public void displaySelectedObjectProperties() {
        if (getSelectedRowCount() != 1) {
            JOptionPane.showMessageDialog(this, "You may only display properties for one object at a time", "",
                    JOptionPane.INFORMATION_MESSAGE);
        }

        String path = null;
        try {
            path = selectedFile.getPath();
            profileModel.getProfileAdapter().displayObjectProperties(selectedFile);
        } catch (Exception e) {
            String msg = DBUtils.getErrorMessage(
                    "An error occurred displaying properties for " + profileModel.getSelectedProfile().decode(path),
                    e);
            LOG.log(Level.WARNING, msg, e);
            GUIHelper.showMessageDialog(this, msg, "Error Displaying Properties", JOptionPane.ERROR_MESSAGE);
        }
    }

    public void mkdirAction() {
        lock();
        new mkdirWorker().execute();
    }

    class mkdirWorker extends SwingWorker {
        @Override
        protected Object doInBackground() throws Exception {
            mkdirActionAux();
            return null;
        }

        @Override
        protected void done() {
            unlock();
        }
    }

    public void mkdirActionAux() {
        try {
            String newDirName;
            String newDirPath = null;

            do {
                // get new directory name from user
                newDirName = (String) JOptionPane.showInputDialog(this.getParent(), "Name of new directory",
                        "New Directory", JOptionPane.QUESTION_MESSAGE, null, null, "directory name");

                // check if user pressed 'cancel' button
                if (newDirName == null) {
                    LOG.info("Mkdir cancelled\n");
                    return;
                }

                /**
                 * Validate the new directory name TODO: Complete validation based to target OS The
                 * forward slash is never valid. On a local file system the file separator is
                 * invalid.
                 */
                ProfileType type = getSelectedProfile().getType();
                if (newDirName.contains("/")
                        || (type == ProfileType.FILESYSTEM && newDirName.contains(File.separator))
                        || (type == ProfileType.HCAP2 && newDirName.contains("&"))) {
                    JOptionPane.showMessageDialog(this.getParent(), "Invalid directory name", "Error",
                            JOptionPane.WARNING_MESSAGE);
                    newDirName = null;
                    continue;
                }

                // build new directory path from current directory and user supplied name. We must
                // encode the part just entered by the user
                String encodedDirName = newDirName;
                try {
                    encodedDirName = profileModel.getSelectedProfile().encode(newDirName);
                } catch (Exception e) {
                    JOptionPane.showMessageDialog(this.getParent(), "An error occurred encoding the directory name",
                            "Error", JOptionPane.WARNING_MESSAGE);
                    LOG.log(Level.WARNING, String.format("An error occurred encoding directory <%s>", newDirName));
                    newDirName = null;
                    continue;
                }

                newDirPath = FileUtil.resolvePath(currentDir.getPath(), encodedDirName,
                        profileModel.getSelectedProfile().getPathSeparator());
            } while (newDirName == null || newDirName.length() <= 0 || newDirPath == null);

            LOG.info("Creating new directory " + newDirPath);
            try {
                if (!arcMoverEngine.mkdir(profileModel.getSelectedProfile(), newDirPath)) {
                    String msg = "Could not create directory: Permission Denied";
                    throw new IOException(msg);
                }
                final ArcMoverDirectory dir = profileModel.getProfileAdapter().getDirectory(newDirPath);
                GUIHelper.invokeAndWait(new Runnable() {
                    public void run() {
                        refreshPathAndFileList();
                        // refresh the other panel if necessary, but it won't do this panel because
                        // we are currently locked
                        HCPDataMigrator.getInstance().refreshMatchingPanels(profileModel.getSelectedProfile(),
                                currentDir);
                    }
                }, "refresh after creating new directory");
            } catch (Exception e) {
                String uiErrorMsg;
                String logErrorMsg;

                if (e instanceof StorageAdapterException) {
                    logErrorMsg = e.getMessage();
                    uiErrorMsg = e.getMessage();
                } else {
                    logErrorMsg = "Could not create directory: " + newDirPath;
                    uiErrorMsg = DBUtils.getErrorMessage(
                            "Error creating directory " + profileModel.getSelectedProfile().decode(newDirPath), e);
                }
                LOG.log(Level.WARNING, logErrorMsg, e);
                GUIHelper.showMessageDialog(this.getParent(), uiErrorMsg, "Create Directory Error",
                        JOptionPane.ERROR_MESSAGE);
            }
        } catch (RuntimeException e1) {
            // TODO: Implement Proper Error Handling
            LOG.log(Level.WARNING, "Unexpected Exception", e1);
            throw e1;
        }
    }

    private void showSSLCert() {
        new SSLCertificateDialog((HCAPProfile) getSelectedProfile()).setVisible(true);
    }

    public ArcMoverFile getSelectedFile() {
        return selectedFile;
    }

    // Version listings are read only
    public boolean isSelectionReadOnly() {
        return selectionIsReadOnly || (currentDir != null && currentDir.isVersionList());
    }

    /**
     * Listener for path changes Basic algorithm: If not forcing a refresh and the new path is the
     * same as the old path don't do anything Lock the panel In an asynchronous worker thread: Call
     * into setFileListToDirectory If error, show an error If successful Update history Update panel
     * Update right-click menu Unlock panel on completion of worker thread
     */
    class PathComboActionListener implements ActionListener {
        // We need to declare the comboBoxChanged action command explicitly -- it is not provided
        // for us.
        private static final String CHANGE_COMMAND = "comboBoxChanged";
        private String currentPath = null;
        private AbstractProfileBase currentProfile = null;

        public void actionPerformed(ActionEvent e) {
            // Only want to deal with "change" commands because of how we deal with state
            // transitions
            if (e.getActionCommand().equals(CHANGE_COMMAND)) {

                String newPath = null;
                if (getSelectedProfile() == FileSystemProfile.LOCAL_FILESYSTEM_PROFILE
                        && pathCombo.getSelectedIndex() == 0
                        && BROWSE_LFS_TEXT.equals(pathCombo.getSelectedItem())) {
                    // we're going to browse the local file system; the first item in the list in
                    // this case was Browse
                    newPath = browseDirectory();
                    forceRefresh = true; // "Browse ..." is currently displayed; need to refresh no
                                         // matter what
                    if (newPath == null) {
                        // the user cancelled the browse action, so we need to go back to the
                        // old path that was displayed. We can't just return because "Browse ... "
                        // is currently displayed as the path
                        newPath = currentPath;
                    }
                } else {
                    newPath = (String) pathCombo.getSelectedItem();
                }

                if (!forceRefresh && currentPath != null && currentPath.equals(newPath)
                        && profileModel.getSelectedProfile() != null
                        && profileModel.getSelectedProfile().equals(currentProfile)) {
                    return;
                }

                lock();
                new SetPathWorker(newPath, currentPath).execute();
            }
        }

        public void setCurrentPath(String path) {
            this.currentPath = path;
        }

        public void setCurrentProfile(AbstractProfileBase profile) {
            this.currentProfile = profile;
        }

        public AbstractProfileBase getCurrentProfile() {
            return currentProfile;
        }
    }

    class PathComboBoxRenderer extends BasicComboBoxRenderer {
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
                boolean cellHasFocus) {

            if (value != null && getSelectedProfile() != null) {
                value = getSelectedProfile().decode((String) value);
            }

            JComponent comp = (JComponent) super.getListCellRendererComponent(list, value, index, isSelected,
                    cellHasFocus);
            comp.setToolTipText(String.valueOf(value));
            return comp;
        }
    }

    /**
     * This editor displays decoded strings, so they are in user-readable form.
     *
     * However, the getItem() and setItem() methods return/take encoded strings.
     *
     * When the user types into the editor, the visible field is in decoded form, so it is encoded
     * when the user accepts the entry (hits enter.) The only issue is that since what is displayed
     * is decoded (and what the user is entering is decoded), if you try to edit (e.g. append to) a
     * decoded string, and the decoded string lost info (i.e. has a replacement char in it), it
     * doesn't work. So, the solution is to simply navigate with clicking into dirs with unsupported
     * chars, instead of trying to manually type at the end.
     */
    class PathComboBoxEditor implements ComboBoxEditor {

        private JTextField editField = new JTextField("", 9);
        private String valueToReturn; // this may be different from the value displayed, as the
                                      // displayed value is decoded

        public PathComboBoxEditor() {
            editField.setBorder(null);

            editField.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    // take the value just typed by the user, encode it, and set the selected item
                    // to that.
                    // If the version prefix is on the path, only encode the portion after the
                    // version prefix.
                    String path = editField.getText().trim();
                    if (isVersioned(path)) {
                        valueToReturn = VERSION_PREFIX
                                + getSelectedProfile().encode(path.substring(VERSION_PREFIX.length()));
                    } else {
                        valueToReturn = getSelectedProfile().encode(path);
                    }
                    pathCombo.setSelectedItem(valueToReturn);
                }
            });
        }

        public Component getEditorComponent() {
            return editField;
        }

        public void setItem(Object anObject) {
            if (anObject != null) {
                valueToReturn = anObject.toString();
                editField.setText(getSelectedProfile().decode(valueToReturn));
            }
        }

        public Object getItem() {
            return valueToReturn;
        }

        public void selectAll() {
            editField.selectAll();
        }

        public void addActionListener(ActionListener l) {
            editField.addActionListener(l);
        }

        public void removeActionListener(ActionListener l) {
            editField.removeActionListener(l);
        }
    }

    /**
     * Returns whether the path represents a versioned file
     * 
     * @param path
     *            The path
     * @return whether the path is versioned (begins with the version prefix)
     */
    private boolean isVersioned(final String path) {
        return path.startsWith(VERSION_PREFIX);
    }

    /**
     * Removes the versioning parts of the path (resolving down to the containing directory) if
     * versioning is not supported by the selected profile
     * 
     * @param path
     *            The path
     * @return The original path if no change is required, the unversioned containing directory
     *         otherwise
     * @throws StorageAdapterException
     *             -
     */
    private String stripVersioningIfUnsupported(String path) throws StorageAdapterException {
        boolean isVersioned = isVersioned(path);
        // We don't care if versioning is supported if we're not looking at a versioned path,
        // this also will short-circuit on isVersioned to prevent a costly call to
        // supportsVersioning
        // which gets proc to determine if the namespace is still versioned
        boolean supportsVersioning = isVersioned && getSelectedProfile().supportsVersioning();
        // If the original path is a versioned path but the namespace no longer supports versioning
        if (isVersioned && !supportsVersioning) {
            // Chop off the versions prefix and the versioned file at the end
            path = path.substring(VERSION_PREFIX.length(), path.lastIndexOf('/') + 1);
        }
        return path;
    }

    /**
     * This is the asynchronous worker thread method where we: Call into setFileListToDirectory If
     * error, show an error If successful Update history Update panel Update right-click menu
     *
     * @param newPath
     *            The new path we're changing to
     * @param oldPath
     *            The old path we're changing from
     */
    private void setPath(String newPath, final String oldPath) {
        ProfilePanelAdapter adapter = profileModel.getProfileAdapter();
        try {
            ArcMoverDirectory dir = null;
            List<String> fileListColumns = adapter.getFileListColumns();

            newPath = stripVersioningIfUnsupported(newPath);
            boolean isVersioned = isVersioned(newPath);

            if (isVersioned) {
                dir = adapter.getVersions(filePath(newPath));
                fileListColumns = ((Hcp3AuthNamepaceProfilePanelAdapter) adapter).getVersionListColumns();
            } else {
                dir = adapter.getDirectory(newPath);
            }
            setFileListToDirectory(dir, fileListColumns);
            AbstractProfileBase selectedProfile = profileModel.getSelectedProfile();
            String[] paths = HistoryManager.addToHistory(selectedProfile, newPath);
            setCurrent(dir, newPath, paths, selectedProfile);
        } catch (Exception e) {
            String msg = "An error occurred reading directory " + profileModel.getSelectedProfile().decode(newPath);
            if (e instanceof StorageAdapterLiteralException) {
                msg = DBUtils.getErrorMessage(msg, e);
            }
            LOG.log(Level.WARNING, msg, e);
            GUIHelper.showMessageDialog(this, msg, "Error Reading Directory", JOptionPane.ERROR_MESSAGE);
            String[] paths = HistoryManager.removeFromHistory(profileModel.getSelectedProfile(), newPath);

            ArcMoverDirectory homeDir = null;
            boolean retreat = true;
            try {
                homeDir = adapter.getHomeDirectory();
            } catch (Exception ie) {
                LOG.log(Level.WARNING, "Could not determine home directory", e);
                retreat = false;
                GUIHelper.invokeAndWait(new Runnable() {
                    public void run() {
                        profileSelectionCombo.setSelectedItem(FileSystemProfile.LOCAL_FILESYSTEM_PROFILE);
                    }
                }, "change to the local file system profile");
            }
            if (retreat) {
                final String homePath = homeDir.getPath();
                if (oldPath.equals(newPath) && oldPath.equals(homePath)) {
                    // Already home, go to LFS
                    forceRefresh = true;
                    GUIHelper.invokeAndWait(new Runnable() {
                        public void run() {
                            profileSelectionCombo.setSelectedItem(FileSystemProfile.LOCAL_FILESYSTEM_PROFILE);
                        }
                    }, "change to the local file system profile");
                } else if (oldPath.equals(newPath)) {
                    // Would just end up here anyway, go home and force us to go through the motions
                    // This setCurrent prevents us from ping-ponging between homePath and newPath
                    setCurrent(homeDir, homePath, paths, getSelectedProfile());
                    forceRefresh = true; // setCurrent cleans refresh
                    GUIHelper.invokeAndWait(new Runnable() {
                        public void run() {
                            pathCombo.setSelectedItem(homePath);
                        }
                    }, "navigate to home directory");
                } else {
                    // Try retreating
                    forceRefresh = true;
                    GUIHelper.invokeAndWait(new Runnable() {
                        public void run() {
                            pathCombo.setSelectedItem(oldPath);
                        }
                    }, "return to previous directory");
                }
            }
        }
    }

    class SetPathWorker extends SwingWorker {
        String newPath;
        String oldPath;

        public SetPathWorker(final String newPath, final String oldPath) {
            super();
            this.newPath = newPath;
            this.oldPath = oldPath;
        }

        @Override
        protected void done() {
            unlock();
        }

        @Override
        protected Object doInBackground() throws Exception {
            setPath(newPath, oldPath);
            return null;
        }
    }

    private class ProfileComboBoxModel extends AbstractListModel implements ComboBoxModel {

        private List<AbstractProfileBase> profiles;

        private AbstractProfileBase selectedProfile;
        private ProfilePanelAdapter selectedAdapter;
        // Using the Map below doesn't work for adapter. The hash code
        // for a profile is changing after it has been put in the map, causing the get on the map to
        // fail when in fact
        // the profile is in the map. So instead of using a Map, just keep a separate List of
        // adapters, indexed the same
        // private Map<AbstractProfileBase, ProfilePanelAdapter> adapters = new
        // HashMap<AbstractProfileBase, ProfilePanelAdapter>();
        private List<ProfilePanelAdapter> adapters;

        public ProfileComboBoxModel() {
            loadProfiles();
        }

        public void loadProfiles() {
            AbstractProfileBase oldProfile = selectedProfile;
            selectedProfile = null;
            selectedAdapter = null;

            profiles = ProfileManager.getProfiles();
            profiles.add(0, FileSystemProfile.LOCAL_FILESYSTEM_PROFILE);

            adapters = new ArrayList<ProfilePanelAdapter>(profiles.size());
            for (AbstractProfileBase profile : profiles) {
                ProfilePanelAdapter adapter = profile.makeAdapter();
                adapters.add(adapter);

                if (profile.equals(oldProfile)) {
                    selectedProfile = profile;
                    selectedAdapter = adapter;
                }
            }

            if (selectedProfile == null) {
                selectedProfile = profiles.get(0);
                selectedAdapter = adapters.get(0);
            }

            setSelectedItem(selectedProfile);

            fireContentsChanged(this, 0, profiles.size());

        }

        /**
         * Returns the length of the list.
         * 
         * @return the length of the list
         */
        public int getSize() {
            return profiles.size();
        }

        /**
         * Returns the value at the specified index.
         * 
         * @param index
         *            the requested index
         * @return the value at <code>index</code>
         */
        public Object getElementAt(int index) {
            return profiles.get(index);
        }

        /**
         * Set the selected item. The implementation of this method should notify all registered
         * <code>ListDataListener</code>s that the contents have changed.
         *
         * @param anItem
         *            the list object to select or <code>null</code> to clear the selection
         */
        public void setSelectedItem(Object anItem) {
            boolean foundProfile = false;
            for (int i = 0; i < profiles.size(); i++) {
                if (profiles.get(i).equals(anItem)) {
                    selectedProfile = profiles.get(i);
                    selectedAdapter = adapters.get(i);
                    foundProfile = true;
                    break;
                }
            }
            if (!foundProfile) {
                throw new RuntimeException("Profile " + anItem + " not found in list");
            }
        }

        /**
         * Returns the selected item
         * 
         * @return The selected item or <code>null</code> if there is no selection
         */
        public Object getSelectedItem() {
            return selectedProfile;
        }

        public AbstractProfileBase getSelectedProfile() {
            return selectedProfile;
        }

        public ProfilePanelAdapter getProfileAdapter() {
            return selectedAdapter;
        }
    }
}