public class ClipboardHelper {

    private Paths paths;

    public void copy(FileSystemPanel source, List<Path> paths) {
        this.paths = new Paths(source, paths, ClipboardAction.COPY);
    }

    public void cut(FileSystemPanel source, List<Path> paths) {
        this.paths = new Paths(source, paths, ClipboardAction.CUT);
    }

    public void clear() {
        this.paths = null;
    }

    public boolean isEmpty() {
        return paths == null;
    }

    public Paths getPaths() {
        return this.paths;
    }

    public static class Paths {

        private FileSystemPanel source;

        private List<Path> paths;

        private ClipboardAction action;

        public Paths(FileSystemPanel source, List<Path> paths, ClipboardAction action) {
            this.source = source;
            this.paths = paths;
            this.action = action;
        }

        public FsConnection getConnection() {
            return source.getConnection();
        }

        public FileSystemPanel getSource() {
            return source;
        }

        public List<Path> getPaths() {
            return paths;
        }

        public void setPaths(List<Path> paths) {
            this.paths = paths;
        }

        public ClipboardAction getAction() {
            return action;
        }
    }
} public class AppConfigProvider {

    private AppConfig config;

    public AppConfigProvider() {
        this.config = readConfig();
    }

    private AppConfig readConfig() {
        File file = getConfigLocation();
        if(!file.exists()) return new AppConfig();
        try {
            return JaxbUtils.unmarshall(file, AppConfig.class);
        } catch(IllegalStateException ex) {
            return new AppConfig();
        }
    }

    public void writeConfig() {
        File file = getConfigLocation();
        JaxbUtils.marshall(config, file);
    }

    private File getConfigLocation() {
        try {
            File dir = getJarLocation();
            return new File(dir, "config.xml");
        } catch (URISyntaxException e) {
            throw new IllegalStateException(e);
        }
    }

    private File getJarLocation() throws URISyntaxException {
        CodeSource codeSource = Application.class.getProtectionDomain().getCodeSource();
        File jarFile = new File(codeSource.getLocation().toURI().getPath());
        return jarFile.getParentFile();
    }

    public AppConfig getConfig() {
        return config;
    }
} @XmlRootElement(name = "connection")
public class AppConnection implements Serializable {

    private String user;

    private String name;

    private List<ConfigProperty> properties = new ArrayList<>();

    public AppConnection() {
    }

    public AppConnection(String user, String name) {
        this.user = user;
        this.name = name;
    }

    public String getUser() {
        return user;
    }

    @XmlElement(name = "user")
    public void setUser(String user) {
        this.user = user;
    }

    public String getName() {
        return name;
    }

    @XmlElement(name = "name")
    public void setName(String name) {
        this.name = name;
    }

    public List<ConfigProperty> getProperties() {
        return properties;
    }

    @XmlElementWrapper(name = "configuration")
    @XmlElement(name = "property")
    public void setProperties(List<ConfigProperty> properties) {
        this.properties = properties;
    }

    @XmlTransient
    public Map<String, String> getPropertiesMap() {
        Map<String, String> result = new LinkedHashMap<>();
        if(this.properties == null) return result;
        for (ConfigProperty property : properties) {
            result.put(property.getName(), property.getValue());
        }
        return result;
    }

    @Override
    public String toString() {
        return name;
    }
} public class FsException extends Exception {

    public FsException() {
    }

    public FsException(String message) {
        super(message);
    }

    public FsException(String message, Throwable cause) {
        super(message, cause);
    }

    public FsException(Throwable cause) {
        super(cause);
    }

    public FsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
} public class ConnectionConfig {

    private final String user;

    private final Configuration configuration;

    public ConnectionConfig(String user, Map<String, String> properties) {
        this.user = user;
        try {
            this.configuration = createConfiguration(properties);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    public ConnectionConfig(String user, Configuration... mergeConfigs) {
        this.user = user;
        this.configuration = mergeConfigurations(mergeConfigs);
    }

    private Configuration mergeConfigurations(Configuration... configs) {
        Configuration conf = new Configuration();
        List<String> nameServices = Lists.newArrayList();
        for (Configuration other : configs) {
            String ns = other.get("dfs.nameservices");
            if(StringUtils.isNotBlank(ns)) {
                nameServices.add(ns);
            }
            conf.addResource(other);
        }
        if(!nameServices.isEmpty()) {
            String ns = StringUtils.join(nameServices, ',');
            conf.set("dfs.nameservices", ns);
        }
        afterConfigCreated(conf);
        return conf;
    }

    private Configuration createConfiguration(Map<String, String> properties) throws IOException {
        Configuration conf = new Configuration();
        for (Map.Entry<String, String> entry : properties.entrySet()) {
            conf.set(entry.getKey(), entry.getValue());
        }
        afterConfigCreated(conf);
        return conf;
    }

    private void afterConfigCreated(Configuration conf) {
        conf.set("fs.hdfs.impl", org.apache.hadoop.hdfs.DistributedFileSystem.class.getName());
        conf.set("fs.file.impl", org.apache.hadoop.fs.LocalFileSystem.class.getName());
        conf.set("fs.default.name", conf.get("fs.defaultFS"));
        conf.set("hadoop.job.ugi", user);
    }

    public String getUser() {
        return user;
    }

    public Configuration getConfiguration() {
        return configuration;
    }
} public class FsConnection implements Closeable {

    private final ConnectionConfig config;

    private Path root;

    private FileSystem fs;

    private FsShell shell;

    private boolean isReadOnly;

    public FsConnection(ConnectionConfig config) {
        this.config = config;
        this.isReadOnly = false;
    }

    protected FileSystem createNewFileSystem(Configuration configuration) throws IOException {
        return FileSystem.newInstance(configuration);
    }

    protected FileSystem createSharedFileSystem(Configuration configuration) throws IOException {
        return this.fs != null ? this.fs : FileSystem.get(configuration);
    }

    public boolean isReadOnly() {
        return isReadOnly;
    }

    public void setReadOnly(boolean readOnly) {
        isReadOnly = readOnly;
    }

    public Path getRoot() {
        return root;
    }

    public void setRoot(Path root) {
        this.root = root;
    }

    private void init() {
        try {
            Configuration configuration = config.getConfiguration();
            UserGroupInformation ugi = UserGroupInformation.createRemoteUser(config.getUser());
            ugi.doAs((PrivilegedExceptionAction<Void>) () -> {
                this.fs = this.createSharedFileSystem(configuration);
                this.shell = new FsShell(configuration, fs);
                return null;
            });
        } catch (IOException | InterruptedException e) {
            throw new IllegalStateException(e);
        }
    }

    public <T> T execute(FsAction<T> action) throws FsException {
        return execute(false, action);
    }

    public <T> T execute(boolean separateConnection, FsAction<T> action) throws FsException {
        if(this.fs == null) {
            init();
        }
        try {
            UserGroupInformation ugi = UserGroupInformation.createRemoteUser(config.getUser());
            return ugi.doAs((PrivilegedExceptionAction<T>) () -> {
                Configuration conf = config.getConfiguration();
                FileSystem actionFs = separateConnection ? this.createNewFileSystem(conf) : this.fs;
                FsShell actionShell = separateConnection ? new FsShell(conf, actionFs) : this.shell;
                T ret = action.execute(actionShell, actionFs);
                if(separateConnection) {
                    actionShell.close();
                    actionFs.close();
                }
                return ret;
            });
        } catch (AccessControlException ex) {
            throw new FsAccessException(ex);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new FsException("Problem invoking file system action: " + ex.getMessage(), ex);
        }
    }

    public FsIterator list(Path path) throws FsException {
        return execute((shell, fs) -> new FsIterator(shell, fs, fs.listLocatedStatus(fsPath(path))));
    }

    public FileStatus status(String path) throws FsException {
        return status(new Path(path));
    }

    public FileStatus status(Path path) throws FsException {
        return execute((shell, fs) -> fs.getFileStatus(fsPath(path)));
    }

    public Map<String, String> fsck(Path path) throws FsException {
        return execute(new FsckAction(fsPath(path)));
    }

    public void copyFromLocal(File[] files, Path dest) throws FsException {
        execute(new CopyFromLocalAction(files, fsPath(dest)));
    }

    public void copyFromLocal(File[] files, Path dest, Progress progress) throws FsException {
        execute(new CopyFromLocalAction(files, fsPath(dest), progress));
    }

    public void copyToLocal(Path[] paths, File dest) throws FsException {
        execute((shell, fs) ->  {
            for (Path path : paths) {
                String src = fsPath(path).toUri().getPath();
                String target = new File(dest, path.getName()).getAbsolutePath();
                shell.copyToLocal(src, target);
            }
            return null;
        });
    }

    public void remove(Path[] paths, boolean skipTrash) throws FsException {
        String[] items = FileSystemUtils.pathsToStrings(fsPath(paths));
        execute((shell, fs) -> {
            shell.rmr(skipTrash, items);
            return null;
        });
    }

    public void mkdir(Path path) throws FsException {
        execute((shell, fs) -> {
            shell.mkdir(fsPath(path).toUri().getPath());
            return null;
        });
    }

    public void touch(Path... paths) throws FsException {
        String[] items = FileSystemUtils.pathsToStrings(fsPath(paths));
        execute((shell, fs) -> {
            shell.touchz(items);
            return null;
        });
    }

    public void copy(Path[] src, Path dest) throws FsException {
        doCopy(src, dest, false);
    }

    public void move(Path[] src, Path dest) throws FsException {
        doCopy(src, dest, true);
    }

    public void rename(Path path, String name) throws FsException {
        execute((shell, fs) -> {
            Path to = new Path(path.getParent(), name);
            fs.rename(fsPath(path), fsPath(to));
            return null;
        });
    }

    public void distCp(String [] from, String to) throws FsException {
        execute((shell, fs) ->  {
            DistCp tool = new DistCp(this.getConfig(), null);
            EnumSet<DistCp.Preserve> preserve = EnumSet.noneOf(DistCp.Preserve.class);
            String[] uris = ArrayUtils.add(from, to);
            tool.copy(preserve, false, false, false, false, uris);
            return null;
        });
    }

    public void archive(String archiveName, Path workingDir, Path[] src, Path dest) throws FsException {
        Validate.isTrue(StringUtils.endsWithIgnoreCase(archiveName, ".har"), "archiveName should end with .har");
        execute(new HadoopArchivesAction(archiveName, fsPath(workingDir), fsPath(src), fsPath(dest)));
    }

    private void doCopy(Path[] src, Path dest, boolean deleteSrc) throws FsException {
        List<String> paths = Lists.newArrayList(FileSystemUtils.pathsToStrings(fsPath(src)));
        execute((shell, fs) -> {
            String target = fsPath(dest).toUri().getPath();
            String first = paths.get(0);
            String second = paths.size() > 2 ? paths.get(1) : target;
            String [] rest;
            if(paths.size() > 2) {
                paths.add(target);
                rest = ArrayUtils.subarray(paths.toArray(new String[paths.size()]), 2, paths.size());
            } else {
                rest = new String[0];
            }

            if(deleteSrc) {
                shell.mv(first, second, rest);
            } else {
                shell.cp(first, second, rest);
            }
            return null;
        });
    }

    public FSDataInputStream read(Path path) throws FsException {
        return execute((shell, fs) -> fs.open(fsPath(path)));
    }

    public boolean exists(Path path) throws FsException {
        return execute((shell, fs) -> fs.exists(fsPath(path)));
    }

    public void emptyTrash() throws FsException {
        execute((shell, fs) -> {
            shell.expunge();
            return null;
        });
    }

    public void cleanup(List<Path> paths, Date olderThan) throws FsException {
        execute(new CleanupAction(fsPath(paths), olderThan));
    }

    public void chmod(Path path, String chmod, boolean recursive) throws FsException {
        execute((shell, fs) -> {
            shell.chmod(recursive, chmod, fsPath(path).toUri().getPath());
            return null;
        });
    }

    public void chown(Path path, String owner, boolean recursive) throws FsException {
        execute((shell, fs) -> {
            String uri = fsPath(path).toUri().getPath();
            FsShellPermissions.changePermissions(fs, fs.getConf(), FsShellPermissions.Op.CHOWN, recursive, owner, uri);
            return null;
        });
    }

    public void chgrp(Path path, String group, boolean recursive) throws FsException {
        execute((shell, fs) -> {
            String uri = fsPath(path).toUri().getPath();
            FsShellPermissions.changePermissions(fs, fs.getConf(), FsShellPermissions.Op.CHGRP, recursive, group, uri);
            return null;
        });
    }

    @Override
    public void close() throws IOException {
        IOUtils.closeQuietly(fs);
        IOUtils.closeQuietly(shell);
    }

    protected Path fsPath(Path path) {
        return path;
    }

    protected Path[] fsPath(Path[] paths) {
        return Arrays.stream(paths).map(this::fsPath).toArray(Path[]::new);
    }

    protected List<Path> fsPath(List<Path> paths) {
        return paths.stream().map(this::fsPath).collect(Collectors.toList());
    }

    protected ConnectionConfig getConnectionConfig() {
        return this.config;
    }

    protected FileSystem getFileSystem() {
        return this.fs;
    }

    public Configuration getConfig() {
        return this.config.getConfiguration();
    }

    public String getUser() {
        return config.getUser();
    }
} public class FileSystemTableModel extends AbstractTableModel {

    private FileStatus path;

    private List<FileItem> children = new ArrayList<>();

    private FsConnection connection;

    private FsIterator iterator;

    private FileBrowserColumn [] columns = createColumns();

    private boolean hasMoreRows;

    private AtomicBoolean loading = new AtomicBoolean(false);

    private List<PathListener> pathListeners = new ArrayList<>();

    public FileSystemTableModel(FsConnection connection, String path) throws FsException {
        this.path = connection.status(path);
        this.connection = connection;
        this.hasMoreRows = true;
        loadRows(this.path);
    }

    public void addPathListener(PathListener listener) {
        this.pathListeners.add(listener);
    }

    private void firePathChanged(Path path) {
        for (PathListener listener : this.pathListeners) {
            listener.onPathChanged(path);
        }
    }

    public Path getCurrentPath() {
        return this.path.getPath();
    }

    @Override
    public int getRowCount() {
        return children.size();
    }

    @Override
    public int getColumnCount() {
        return columns.length;
    }

    @Override
    public String getColumnName(int column) {
        return this.columns[column].getName();
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return this.columns[columnIndex].getColumnClass();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        if(rowIndex >= children.size()) return null;
        FileItem child = children.get(rowIndex);
        return columns[columnIndex].getColumnProvider().apply(child);
    }

    public void onFileClicked(Path value) throws FsException {
        if(value == null) return;
        FileStatus status = connection.status(value);
        if(status.isFile()) return;
        loading.set(true);
        loadRows(status);
    }

    public void onFileClicked(int idx) throws FsException {
        if(idx < 0 || idx > children.size()) return;
        FileItem parent = children.get(idx);
        FileStatus status = parent.getStatus();
        if(status == null) {
            Path target = path.getPath().getParent();
            status = connection.status(target);
        } else if(status.isFile()) {
            return;
        }
        loading.set(true);
        loadRows(status);
    }

    public void reloadView() throws FsException {
        loading.set(true);
        loadRows(this.path);
    }

    public void loadMoreRows() {
        if(!hasMoreRows || !loading.compareAndSet(false, true)) return;
        fetchRows(100, false);
    }

    public void loadAllRows() {
        if(!hasMoreRows || !loading.compareAndSet(false, true)) return;
        fetchRows(Integer.MAX_VALUE, false);
    }

    private void loadRows(FileStatus parent) throws FsException {
        FsIterator iterator = this.connection.list(parent.getPath());
        this.path = parent;
        this.iterator = iterator;
        this.children.clear();
        if(this.path.getPath().getParent() != null) {
            this.children.add(FileItem.parent());
        }
        this.hasMoreRows = true;
        fetchRows(100, true);
        firePathChanged(this.path.getPath());
    }

    private void fetchRows(int rows, boolean initial) {
        try {
            doFetchRows(rows, initial);
        } finally {
            loading.set(false);
        }
    }

    private void doFetchRows(int rows, boolean initial) {
        if(iterator == null) return;
        int startSize = this.children.size();
        int idx = 0;
        while(iterator.hasNext() && idx < rows) {
            FileStatus file = iterator.next();
            children.add(new FileItem(file));
            idx++;
        }
        if(!iterator.hasNext()) {
            this.hasMoreRows = false;
        }

        if(initial) {
            fireTableDataChanged();
            return;
        }

        int endSize = this.children.size();
        if(endSize != startSize) {
            fireTableRowsInserted(startSize, endSize - 1);
        }
    }

    private FileBrowserColumn[] createColumns() {
        return new FileBrowserColumn[] {
            FileBrowserColumn.create("", Icon.class, FileItem::getIcon).minWidth(20).maxWidth(20),
            FileBrowserColumn.create("Name", FileItem::getName).sortable(true),
            FileBrowserColumn.create("Group", FileItem::getGroup).sortable(true),
            FileBrowserColumn.create("Owner", FileItem::getOwner).sortable(true),
            FileBrowserColumn.create("Permissions", FileItem::getPermissions).minWidth(90).maxWidth(90),
            FileBrowserColumn.create("Modified", FileItem.FileTimestamp.class, FileItem::getModificationTime)
                    .minWidth(130).sortable(true),
            FileBrowserColumn.create("Size", FileItem.FileSize.class, FileItem::getSize).sortable(true),
        };
    }

    public boolean hasMoreRows() {
        return this.hasMoreRows;
    }

    public int getColumnIndex(String name) {
        for (int i = 0; i < this.columns.length; i++) {
            if(this.columns[i].getName().equalsIgnoreCase(name)) return i;
        }
        return -1;
    }

    public FileBrowserColumn getColumn(int idx) {
        return this.columns[idx];
    }

    public FileItem getRow(int idx) {
        return this.children.get(idx);
    }
} public class StatusBarHelper {

    private final AppConfigProvider provider;

    public StatusBarHelper(AppConfigProvider provider) {
        this.provider = provider;
    }

    public void addLocation(AppLocation location) {
        provider.getConfig().getLocations().add(location);
        provider.writeConfig();
    }

    public WebPopupMenu createConnectionsPopup(MainPanel parent) {
        WebPopupMenu result = new WebPopupMenu();
        List<AppConnection> connections = provider.getConfig().getConnections();
        for (AppConnection connection : connections) {
            result.add(createConnectionItem(connection, parent));
        }
        if(!connections.isEmpty()) {
            result.addSeparator();
        }
        result.add(createEditConnectionsItem(parent));
        return result;
    }

    private WebMenuItem createConnectionItem(AppConnection connection, MainPanel parent) {
        WebMenuItem result = new WebMenuItem(connection.getName());
        result.addActionListener((event) -> parent.openFileSystemTab(connection));
        return result;
    }

    private WebMenuItem createEditConnectionsItem(MainPanel parent) {
        WebMenuItem result = new WebMenuItem("Edit connections", IconFactory.getIcon("connection"));
        result.addActionListener((event) -> parent.showConnectionsDialog());
        return result;
    }

    public WebPopupMenu createLocationsPopup(MainPanel parent) {
        WebPopupMenu result = new WebPopupMenu();
        List<AppLocation> locations = provider.getConfig().getLocations();
        for (AppLocation location : locations) {
            result.add(createLocationItem(location, parent));
        }
        if(!locations.isEmpty()) {
            result.addSeparator();
            result.add(createClearLocationsItem());
        }
        if(parent.getActiveDocument() != null) {
            result.add(createAddLocationItem(parent));
        } else if(locations.isEmpty()) {
            result.add(new WebMenuItem("No saved locations"));
        }

        return result;
    }

    private WebMenuItem createLocationItem(AppLocation location, MainPanel parent) {
        WebMenuItem result = new WebMenuItem(location.getLocationString());
        result.addActionListener((event) -> openLocation(location, parent));
        return result;
    }

    private WebMenuItem createAddLocationItem(MainPanel parent) {
        WebMenuItem result = new WebMenuItem("Add current directory", IconFactory.getIcon("locations"));
        result.addActionListener((event) -> addLocation(parent));
        return result;
    }

    private WebMenuItem createClearLocationsItem() {
        WebMenuItem result = new WebMenuItem("Clear locations", IconFactory.getIcon("locations-clear"));
        result.addActionListener((event) -> clearLocations());
        return result;
    }

    private void openLocation(AppLocation location, MainPanel parent) {
        DocumentData doc = parent.getActiveDocument();
        if(findMatchingPanel(location, doc)) {
            parent.setActiveDocument(doc);
            return;
        }

        List<DocumentData> documents = parent.getAllDocuments();
        for (DocumentData document : documents) {
            if(findMatchingPanel(location, document)) {
                parent.setActiveDocument(doc);
                return;
            }
        }

        openNewConnection(location, parent);
    }

    private void openNewConnection(AppLocation location, MainPanel parent) {
        List<AppConnection> connections = this.provider.getConfig().getConnections();
        AppConnection found = null;
        for (AppConnection connection : connections) {
            if(connection.getName().equals(location.getConnectionId())) {
                found = connection;
                break;
            }
        }

        if(found == null) {
            ViewUtils.error(parent, "No matching connection found for selected location.");
        } else {
            parent.openFileSystemTab(BeanFactory.connection(found), found.getName(), location.getPath());
        }
    }

    private boolean findMatchingPanel(AppLocation location, DocumentData data) {
        if(data == null) return false;

        if(StringUtils.equals(data.getTitle(), location.getConnectionId())) {
            FileSystemPanel panel = (FileSystemPanel) data.getComponent();
            panel.setCurrentPath(location.getPath());
            return true;
        }
        return false;
    }

    private void addLocation(MainPanel parent) {
        DocumentData data = parent.getActiveDocument();
        if(data == null) return;

        FileSystemPanel panel = (FileSystemPanel) data.getComponent();
        if(panel == null || panel.getConnection() instanceof HarFsConnection) return;

        String connectionId = data.getTitle();
        Path path = panel.getModel().getCurrentPath();
        tryAddLocation(new AppLocation(connectionId, path.toUri().getPath()));
    }

    private void tryAddLocation(AppLocation location) {
        List<AppLocation> locations = this.provider.getConfig().getLocations();
        if(locations.contains(location)) return;

        locations.add(location);
        this.provider.writeConfig();
    }

    private void clearLocations() {
        this.provider.getConfig().getLocations().clear();
        this.provider.writeConfig();
    }
} public class MainPanel extends WebPanel {

    private final WebDocumentPane<DocumentData> pane;

    private final WebStatusBar statusBar;

    private final AppConfigProvider configProvider;

    private final ClipboardHelper clipboard;

    private final ListeningExecutorService executor;

    private final StatusBarHelper statusBarHelper;

    private ConnectionsDialog connectionsDialog;

    public MainPanel(AppConfigProvider configProvider, ClipboardHelper clipboard,
                     ListeningExecutorService executor, StatusBarHelper statusBarHelper) {
        super(new BorderLayout());
        this.executor = executor;
        this.configProvider = configProvider;
        this.statusBarHelper = statusBarHelper;
        this.pane = createDocumentPane();
        this.statusBar = createStatusBar();
        this.clipboard = clipboard;
        initView();
    }

    private void initView() {
        add(this.pane, BorderLayout.CENTER);
        add(this.statusBar, BorderLayout.PAGE_END);
    }

    private WebDocumentPane<DocumentData> createDocumentPane() {
        WebDocumentPane<DocumentData> result = new WebDocumentPane<>();
        result.setTabMenuEnabled(true);
        result.addDocumentListener(new DocumentAdapter<DocumentData>() {
            @Override
            public void closed(DocumentData document, PaneData<DocumentData> pane, int index) {
                FileSystemPanel comp = (FileSystemPanel) document.getComponent();
                IOUtils.closeQuietly(comp);
            }
        });
        return result;
    }

    private WebStatusBar createStatusBar() {
        WebStatusBar result = new WebStatusBar();
        result.add(this.createButton("Connect", "connection", this::showConnections));
        result.add(this.createButton("Locations", "locations", this::showLocations));
        result.add(this.createButton("About", "about", (event) -> showAboutDialog()));

        WebMemoryBar memoryBar = new WebMemoryBar ();
        memoryBar.setPreferredWidth(memoryBar.getPreferredSize().width + 20);
        memoryBar.setFontSize(11);
        result.add(memoryBar, ToolbarLayout.END);
        return result;
    }

    private WebButton createButton(String text, String icon, ActionListener listener) {
        WebButton button = new WebButton(text, IconFactory.getIcon(icon));
        button.setFontSize(11);
        button.addActionListener(listener);
        button.setMinimumWidth(80);
        return button;
    }

    public void showConnectionsDialog() {
        if(this.connectionsDialog == null) {
            Window window = SwingUtils.getWindowAncestor(this);
            this.connectionsDialog = new ConnectionsDialog(this.configProvider, window);
            this.connectionsDialog.addConnectListener(this::onConnect);
        }
        this.connectionsDialog.setVisible(true);
    }

    private void showAboutDialog() {
        try {
            doShowAboutDialog();
        } catch (IOException e) {
            ViewUtils.error(this, e.getMessage());
        }
    }

    private void showPopup(ActionEvent event, Supplier<WebPopupMenu> supplier) {
        WebButton button = (WebButton) event.getSource();
        WebPopupMenu popup = supplier.get();
        popup.setPopupMenuWay(PopupMenuWay.aboveStart);
        popup.show(button, button.getX() - button.getWidth(), button.getY());
    }

    void showLocations(ActionEvent event) {
        showPopup(event, () -> this.statusBarHelper.createLocationsPopup(this));
    }

    void showConnections(ActionEvent event) {
        showPopup(event, () -> this.statusBarHelper.createConnectionsPopup(this));
    }

    private void doShowAboutDialog() throws IOException {
        Resource resource = new ClassPathResource("about.html");
        String text = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8);
        Map<String, String> params = new HashMap<>();
        params.put("fullVersionString", Version.getFullVersionString());
        ViewUtils.message(this, MessageLevel.Info, "", StrSubstitutor.replace(text, params));
    }

    private void onConnect(AppConnection connection) {
        openFileSystemTab(connection);
    }

    public void openFileSystemTab(AppConnection connection) {
        openFileSystemTab(BeanFactory.connection(connection), connection.getName());
    }

    public void openFileSystemTab(FsConnection connection, String name) {
        openFileSystemTab(connection, name, null);
    }

    public void openFileSystemTab(FsConnection connection, String name, String workingDirectory) {
        try {
            FileSystemPanel fspanel = new FileSystemPanel(connection, this.clipboard, this.executor);
            if(StringUtils.isNotBlank(workingDirectory)) {
                fspanel.setCurrentPath(workingDirectory);
            }
            String id = RandomStringUtils.randomAlphabetic(32);
            DocumentData document = new DocumentData(id, IconFactory.getIcon("disk"), name, fspanel);
            this.pane.openDocument(document);
        } catch (RuntimeException ex) {
            ViewUtils.error(this, "Error opening HDFS connection:\n" + ex.getMessage());
        }
    }

    public DocumentData getActiveDocument() {
        return this.pane.getSelectedDocument();
    }

    public List<DocumentData> getAllDocuments() {
        return this.pane.getDocuments();
    }

    public void setActiveDocument(DocumentData data) {
        this.pane.setSelected(data);
    }

    public boolean hasConnections() {
        return !this.configProvider.getConfig().getConnections().isEmpty();
    }

    public WebButton getStatusBarButton(String text) {
        Component[] components = statusBar.getComponents();
        for (Component component : components) {
            if(component instanceof WebButton) {
                if(StringUtils.equalsIgnoreCase(((WebButton) component).getText(), text)) {
                    return (WebButton) component;
                }
            }
        }
        return null;
    }
} public class MainWindow extends WebFrame {

    private final MainPanel panel;

    public MainWindow(MainPanel panel) {
        this.panel = panel;
        initView();
    }

    private void initView() {
        setMinimumSize(new Dimension(800, 600));
        setContentPane(this.panel);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setTitle("FSBrowser");
        setLocationRelativeTo(null);
        pack();
    }

    public void run() {
        setVisible(true);
        if(panel.hasConnections()) {
            WebButton connect = panel.getStatusBarButton("Connect");
            if(connect != null) connect.doClick();
        } else {
            panel.showConnectionsDialog();
        }
    }
}
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import pl.sdadas.fsbrowser.app.clipboard.ClipboardHelper;
import pl.sdadas.fsbrowser.app.config.AppConfigProvider;
import pl.sdadas.fsbrowser.app.config.AppConnection;
import pl.sdadas.fsbrowser.exception.FsException;
import pl.sdadas.fsbrowser.fs.connection.ConnectionConfig;
import pl.sdadas.fsbrowser.fs.connection.FsConnection;
import pl.sdadas.fsbrowser.view.filebrowser.FileSystemTableModel;
import pl.sdadas.fsbrowser.view.locations.StatusBarHelper;
import pl.sdadas.fsbrowser.view.mainwindow.MainPanel;
import pl.sdadas.fsbrowser.view.mainwindow.MainWindow;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
package pl.sdadas.fsbrowser.app;



/**
 * @author Sławomir Dadas
 */
public final class BeanFactory {

    private static Map<Class<?>, Object> beans = new HashMap<>();

    private BeanFactory() {
    }

    public static FsConnection connection(AppConnection connection) {
        ConnectionConfig config = new ConnectionConfig(connection.getUser(), connection.getPropertiesMap());
        return new FsConnection(config);
    }

    public static FileSystemTableModel tableModel(FsConnection connection) {
        try {
            return new FileSystemTableModel(connection, "/");
        } catch (FsException e) {
            throw new IllegalStateException(e);
        }
    }

    public static ClipboardHelper clipboardHelper() {