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() {
        return singleton(ClipboardHelper.class, ClipboardHelper::new);
    }

    public static MainPanel mainPanel() {
        return singleton(MainPanel.class, () -> {
            return new MainPanel(configProvider(), clipboardHelper(), executorService(), statusBarHelper());
        });
    }

    public static MainWindow mainWindow() {
        return singleton(MainWindow.class, () -> new MainWindow(mainPanel()));
    }

    public static AppConfigProvider configProvider() {
        return singleton(AppConfigProvider.class, AppConfigProvider::new);
    }

    public static ListeningExecutorService executorService() {
        return singleton(ListeningExecutorService.class, () -> {
            ExecutorService executor = Executors.newFixedThreadPool(3);
            return MoreExecutors.listeningDecorator(executor);
        });
    }

    public static StatusBarHelper statusBarHelper() {
        return singleton(StatusBarHelper.class, () -> {
            return new StatusBarHelper(configProvider());
        });
    }

    @SuppressWarnings("unchecked")
    private static <T> T singleton(Class<T> clazz, Supplier<T> supplier) {
        return (T) beans.computeIfAbsent(clazz, key -> supplier.get());
    }
} public enum ClipboardAction {
    CUT, COPY;
} 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 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 HarFsConnection extends FsConnection {

    private final FileSystem parentFileSystem;

    public HarFsConnection(FsConnection other, Path harFile) {
        super(other.getConnectionConfig());
        this.setReadOnly(true);
        this.setRoot(getHarPath(harFile));
        this.parentFileSystem = new CloseSuppressingFileSystem(other.getFileSystem());
    }

    @Override
    protected FileSystem createNewFileSystem(Configuration configuration) throws IOException {
        HarFileSystem fs = new HarFileSystem(this.parentFileSystem);
        fs.initialize(this.getRoot().toUri(), configuration);
        return fs;
    }

    @Override
    protected FileSystem createSharedFileSystem(Configuration configuration) throws IOException {
        return this.getFileSystem() != null ? this.getFileSystem() : createNewFileSystem(configuration);
    }

    private URI getHarUri(Path file) {
        URI uri = file.toUri();
        try {
            return new URI("har", uri.getScheme() + "-" + uri.getAuthority(), uri.getPath(), uri.getFragment());
        } catch (URISyntaxException e) {
            throw new IllegalStateException(e);
        }
    }

    private Path getHarPath(Path file) {
        return new Path(getHarUri(file));
    }

    @Override
    protected Path fsPath(Path path) {
        String relative = StringUtils.stripToNull(StringUtils.removeStart(path.toUri().getPath(), "/"));
        return relative != null ? path : this.getRoot();
    }
} public class FileSystemUtils {

    public static boolean isSameFileSystem(FsConnection first, FsConnection second) {
        String firstName = getDefaultFs(first);
        String secondName = getDefaultFs(second);
        return StringUtils.equals(firstName, secondName);
    }

    public static String getDefaultFs(FsConnection connection) {
        Configuration config = connection.getConfig();
        return ObjectUtils.firstNonNull(config.get("fs.defaultFS"), config.get("fs.default.name"));
    }

    public static String formatByteCount(long bytes) {
        return formatByteCount(bytes, false);
    }

    public static String formatByteCount(long bytes, boolean si) {
        int unit = getMetricUnit(si);
        if (bytes < unit) return bytes + " B";
        int exp = (int) (Math.log(bytes) / Math.log(unit));
        String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "i" : "");
        return String.format("%.2f %sB", bytes / Math.pow(unit, exp), pre);
    }

    public static boolean checkNativeLibraries() {
        if(Shell.WINDOWS && StringUtils.isBlank(Shell.WINUTILS)) {
            return false;
        }
        return true;
    }

    public static boolean isParentPath(Path parent, Path child) {
        String parentPath = parent.toUri().getPath();
        String childPath = child.toUri().getPath();
        return childPath.startsWith(parentPath);
    }

    public static String[] pathsToStrings(Path... paths) {
        return Arrays.stream(paths).map(path -> path.toUri().getPath()).toArray(String[]::new);
    }

    private static int getMetricUnit(boolean si) {
        return si ? 1000 : 1024;
    }

    private FileSystemUtils() {
    }
} public final class ViewUtils {

    public static void setupDialogWindow(JDialog dialog) {
        dialog.pack();
        dialog.setMinimumSize(dialog.getSize());
        dialog.setLocationRelativeTo(dialog.getOwner());
    }

    public static WebPanel rightLeftPanel(JComponent... comps) {
        return horizontalPanel(ComponentOrientation.RIGHT_TO_LEFT, null, comps);
    }

    public static WebPanel leftRightPanel(JComponent... comps) {
        return horizontalPanel(ComponentOrientation.LEFT_TO_RIGHT, null, comps);
    }

    public static WebPanel rightLeftPanel(Integer minWidth, JComponent... comps) {
        return horizontalPanel(ComponentOrientation.RIGHT_TO_LEFT, minWidth, comps);
    }

    public static WebPanel leftRightPanel(Integer minWidth, JComponent... comps) {
        return horizontalPanel(ComponentOrientation.LEFT_TO_RIGHT, minWidth, comps);
    }

    public static WebPanel horizontalPanel(ComponentOrientation orientation, Integer minWidth, JComponent... comps) {
        HorizontalFlowLayout layout = new HorizontalFlowLayout();
        WebPanel panel = new WebPanel(layout);
        panel.setComponentOrientation(orientation);
        for (JComponent button : comps) {
            if(minWidth != null) SizeUtils.setMinimumWidth(button, minWidth);
            panel.add(button);
        }
        return panel;
    }

    public static MessagePopup error(Component comp, String message) {
        return message(comp, MessageLevel.Error, "Error", message);
    }

    public static MessagePopup warning(Component comp, String message) {
        return message(comp, MessageLevel.Warning, "Warning", message);
    }

    public static MessagePopup info(Component comp, String message) {
        return message(comp, MessageLevel.Info, "Information", message);
    }

    public static MessagePopup message(Component comp, MessageLevel level, String title, String message) {
        Window window = SwingUtils.getWindowAncestor(comp);
        MessagePopup result = new MessagePopup(window);
        result.setLevel(level);
        result.setTitle(title);
        result.setText(message);
        result.pack();
        int x = window.getX() + (window.getWidth() / 2) - (result.getWidth() / 2);
        int y = window.getY() + (window.getHeight() / 2) - (result.getHeight() / 2);
        result.show(x, y);
        return result;
    }

    public static void handleErrors(Component comp, UnsafeRunnable runnable) {
        try {
            runnable.run();
        } catch(FsAccessException ex) {
            SwingUtils.invokeLater(() -> error(comp, "Access denied"));
        } catch(FsException ex) {
            ex.printStackTrace();
            SwingUtils.invokeLater(() -> error(comp, ex.getMessage()));
        }
    }

    public static void ignoreErrors(UnsafeRunnable runnable) {
        try {
            runnable.run();
        } catch (FsException ex) {
            ex.printStackTrace();
        }
    }

    public static boolean requireNativeLibraries(JComponent comp) {
        if(!FileSystemUtils.checkNativeLibraries()) {
            String message = "This action requires hadoop native libraries.\n" +
                    "Please install them, set HADOOP_HOME environment variable and restart application.\n";
            warning(comp, message);
            return false;
        }
        return true;
    }

    private ViewUtils() {
    }
} public class ChmodDialog extends WebDialog {

    private WebPanel panel;

    private Set<PermissionKey> permissions = new HashSet<>();

    private boolean recursive = true;

    private boolean stickyBit;

    private String chmod;

    public ChmodDialog(FsConnection connection, Path path, Window owner) {
        super(owner);
        readPermissions(connection, path);
        initView();
    }

    private void readPermissions(FsConnection connection, Path path) {
        ViewUtils.handleErrors(this, () -> {
            FileStatus status = connection.status(path);
            FsPermission permission = status.getPermission();
            this.stickyBit = permission.getStickyBit();
            this.permissions.clear();
            readPermissionsSubject(permission.getUserAction(), PermissionSubject.User);
            readPermissionsSubject(permission.getGroupAction(), PermissionSubject.Group);
            readPermissionsSubject(permission.getOtherAction(), PermissionSubject.Other);
        });
    }

    private void readPermissionsSubject(FsAction actions, PermissionSubject subject) {
        EnumSet<FsAction> read = EnumSet.of(READ, READ_WRITE, READ_EXECUTE, ALL);
        if(read.contains(actions)) permissions.add(new PermissionKey(subject, PermissionType.Read));

        EnumSet<FsAction> write = EnumSet.of(WRITE, WRITE_EXECUTE, READ_WRITE, ALL);
        if(write.contains(actions)) permissions.add(new PermissionKey(subject, PermissionType.Write));

        EnumSet<FsAction> exec = EnumSet.of(EXECUTE, WRITE_EXECUTE, READ_EXECUTE, ALL);
        if(exec.contains(actions)) permissions.add(new PermissionKey(subject, PermissionType.Execute));
    }

    private String getChmodValue() {
        char[] result = new char[4];
        result[0] = stickyBit ? '1' : '0';
        result[1] = getChmodSubject(PermissionSubject.User);
        result[2] = getChmodSubject(PermissionSubject.Group);
        result[3] = getChmodSubject(PermissionSubject.Other);
        return new String(result);
    }

    private char getChmodSubject(PermissionSubject subject) {
        int res = permissions.stream()
                .filter(key -> key.subject.equals(subject))
                .mapToInt(key -> key.getType().value).sum();
        return String.valueOf(res).charAt(0);
    }

    private void initView() {
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setTitle("Change permissions");
        setResizable(true);
        setModal(true);

        WebPanel userPanel = permissionsPanel(PermissionSubject.User);
        WebPanel groupPanel = permissionsPanel(PermissionSubject.Group);
        WebPanel otherPanel = permissionsPanel(PermissionSubject.Other);
        WebCheckBox recursiveCheckbox = new WebCheckBox("Recursive");
        recursiveCheckbox.setSelected(this.recursive);
        recursiveCheckbox.addItemListener(event -> this.recursive = event.getStateChange() == ItemEvent.SELECTED);
        WebCheckBox stickyBitCheckbox = new WebCheckBox("Sticky bit");
        stickyBitCheckbox.setSelected(this.stickyBit);
        stickyBitCheckbox.addItemListener(event -> this.stickyBit = event.getStateChange() == ItemEvent.SELECTED);
        stickyBitCheckbox.setMargin(0, 10, 0, 0);
        WebPanel optionsPanel = ViewUtils.leftRightPanel(recursiveCheckbox, stickyBitCheckbox);
        optionsPanel.setMargin(10, 0, 20, 0);
        WebPanel buttonsPanel = createButtonsPanel();

        VerticalFlowLayout layout = new VerticalFlowLayout();
        this.panel = new WebPanel(layout);
        this.panel.setMargin(10);
        this.panel.add(userPanel, groupPanel, otherPanel, optionsPanel, buttonsPanel);

        add(this.panel);
        ViewUtils.setupDialogWindow(this);
    }

    private WebPanel createButtonsPanel() {
        return ViewUtils.rightLeftPanel(50, new WebButton("Cancel", this::reject), new WebButton("Ok", this::accept));
    }

    private void accept(ActionEvent event) {
        this.chmod = getChmodValue();
        setVisible(false);
    }

    private void reject(ActionEvent event) {
        this.chmod = null;
        setVisible(false);
    }

    public String showDialog() {
        setVisible(true);
        return chmod;
    }

    public boolean isRecursive() {
        return recursive;
    }

    private WebPanel permissionsPanel(PermissionSubject subject) {
        WebLabel label = new WebLabel(subject.name());
        label.setPreferredWidth(70);
        WebButtonGroup group = new WebButtonGroup(false);
        group.add(createButton(subject, PermissionType.Read));
        group.add(createButton(subject, PermissionType.Write));
        group.add(createButton(subject, PermissionType.Execute));
        return ViewUtils.leftRightPanel(label, group);
    }

    private WebToggleButton createButton(PermissionSubject subject, PermissionType type) {
        WebToggleButton result = new WebToggleButton(type.code);
        result.setDrawFocus(false);
        result.addItemListener(event -> {
            if(event.getStateChange() == ItemEvent.SELECTED) {
                permissions.add(new PermissionKey(subject, type));
            } else if(event.getStateChange() == ItemEvent.DESELECTED) {
                permissions.remove(new PermissionKey(subject, type));
            }
        });
        result.setSelected(permissions.contains(new PermissionKey(subject, type)));
        result.setPreferredWidth(30);
        return result;
    }

    private class PermissionKey {

        private final PermissionSubject subject;

        private final PermissionType type;

        public PermissionKey(PermissionSubject subject, PermissionType type) {
            this.subject = subject;
            this.type = type;
        }

        public PermissionSubject getSubject() {
            return subject;
        }

        public PermissionType getType() {
            return type;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) return true;
            if (other == null) return false;

            if (other instanceof PermissionKey) {
                PermissionKey that = (PermissionKey) other;
                return new EqualsBuilder()
                        .append(subject, that.subject)
                        .append(type, that.type)
                        .isEquals();
            }
            return false;
        }

        @Override
        public int hashCode() {
            return Objects.hash(subject, type);
        }
    }

    private enum PermissionSubject {
        User, Group, Other;
    }

    private enum PermissionType {
        Read("R", 4), Write("W", 2), Execute("X", 1);

        private final String code;

        private final int value;

        PermissionType(String code, int value) {
            this.code = code;
            this.value = value;
        }
    }

} public class ChownDialog extends WebDialog {

    private String owner;

    private String group;

    private boolean recursive;

    private WebTextField ownerField;

    private WebTextField groupField;

    private WebPanel panel;

    private Result result;

    public ChownDialog(FileItem file, Window owner) {
        super(owner);
        this.owner = file.getOwner();
        this.group = file.getGroup();
        initView();
    }

    private void initView() {
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setTitle("Change owner/group");
        setMinimumSize(new Dimension(300, 150));
        setResizable(true);
        setModal(true);

        FormLayout formLayout = new FormLayout(5, 5);
        WebPanel formPanel = new WebPanel(formLayout);
        this.ownerField = new WebTextField(this.owner);
        this.groupField = new WebTextField(this.group);
        formPanel.setMargin(0, 0, 10, 0);
        formPanel.add(new WebLabel("Owner"));
        formPanel.add(ownerField);
        formPanel.add(new WebLabel("Group"));
        formPanel.add(groupField);

        WebCheckBox recursiveCheckbox = new WebCheckBox("Recursive");
        recursiveCheckbox.setSelected(this.recursive);
        recursiveCheckbox.addItemListener(event -> this.recursive = event.getStateChange() == ItemEvent.SELECTED);

        VerticalFlowLayout layout = new VerticalFlowLayout();
        this.panel = new WebPanel(layout);
        this.panel.setMargin(10);
        this.panel.add(formPanel);
        this.panel.add(recursiveCheckbox);
        this.panel.add(createButtonsPanel());

        add(this.panel);
        ViewUtils.setupDialogWindow(this);
    }

    private WebPanel createButtonsPanel() {
        return ViewUtils.rightLeftPanel(50, new WebButton("Cancel", this::reject), new WebButton("Ok", this::accept));
    }

    private void accept(ActionEvent event) {
        this.result = new Result();
        this.result.owner = getChangedValue(ownerField, owner);
        this.result.group = getChangedValue(groupField, group);
        this.result.recursive = recursive;
        setVisible(false);
    }

    private String getChangedValue(WebTextField field, String oldValue) {
        String value = StringUtils.strip(field.getText());
        if(StringUtils.isBlank(value)) return null;
        if(StringUtils.equals(value, oldValue)) return null;
        return value;
    }

    private void reject(ActionEvent event) {
        this.result = null;
        setVisible(false);
    }

    public Result showDialog() {
        setVisible(true);
        return result;
    }

    public static class Result {

        private String owner;

        private String group;

        private boolean recursive;

        public String getOwner() {
            return owner;
        }

        public String getGroup() {
            return group;
        }

        public boolean isRecursive() {
            return recursive;
        }
    }

} public class CleanupDialog extends WebDialog {

    private final FsConnection connection;

    private CheckBoxListModel listModel;

    private WebCheckBoxList list;

    private WebPanel panel;

    private SpinnerDateModel dateModel;

    private Set<String> paths = new HashSet<>();

    private Result result = null;

    public CleanupDialog(FsConnection connection, Window owner) {
        super(owner);
        this.connection = connection;
        this.listModel = createCheckboxListModel();
        this.dateModel = createSpinnerDateModel();
        initView();
    }

    private void initView() {
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setTitle("Cleanup");
        setResizable(true);
        setModal(true);

        this.list = createDirectoryList();
        WebLabel listLabel = new WebLabel("Select directories to cleanup:");
        WebScrollPane scroll = new WebScrollPane(this.list);
        scroll.setPreferredHeight(100);
        WebLinkLabel addDirButton = new WebLinkLabel(IconFactory.getIcon("folder-add"));
        addDirButton.setLink("Add directory", this::addDirectory);
        addDirButton.setHorizontalAlignment(SwingConstants.LEFT);
        WebLabel optionsLabel = new WebLabel("Delete files older than: ");
        WebSpinner dateSpinner = new WebSpinner();
        dateSpinner.setModel(dateModel);
        WebPanel optionsPanel = ViewUtils.leftRightPanel(optionsLabel, dateSpinner);
        WebPanel buttonsPanel = createButtonsPanel();
        buttonsPanel.setMargin(10, 0, 0, 0);

        VerticalFlowLayout layout = new VerticalFlowLayout();
        this.panel = new WebPanel(layout);
        this.panel.setMargin(10);
        this.panel.add(listLabel, scroll, addDirButton, optionsPanel, buttonsPanel);

        add(this.panel);
        ViewUtils.setupDialogWindow(this);
    }

    private WebPanel createButtonsPanel() {
        return ViewUtils.rightLeftPanel(50, new WebButton("Cancel", this::reject), new WebButton("Ok", this::accept));
    }

    @SuppressWarnings("unchecked")
    private void accept(ActionEvent event) {
        List<?> paths = this.listModel.getCheckedValues();
        this.result = new Result((List<Path>) paths, this.dateModel.getDate());
        setVisible(false);
    }

    private void reject(ActionEvent event) {
        setVisible(false);
    }

    private void addDirectory() {
        Object value = WebOptionPane.showInputDialog(this, "Directory name", "Add directory",
                WebOptionPane.QUESTION_MESSAGE, null, null, "/user/" + this.connection.getUser() + "/tmp");
        if(value != null && StringUtils.isNotBlank(value.toString())) {
            addModelPath(value.toString(), this.listModel, true, false);
        }
    }

    private WebCheckBoxList createDirectoryList() {
        return new WebCheckBoxList(this.listModel);
    }

    private SpinnerDateModel createSpinnerDateModel() {
        SpinnerDateModel result = new SpinnerDateModel();
        result.setEnd(new Date());
        result.setValue(DateUtils.addDays(new Date(), -7));
        result.setCalendarField(Calendar.DAY_OF_MONTH);
        return result;
    }

    private CheckBoxListModel createCheckboxListModel() {
        String user = this.connection.getUser();
        CheckBoxListModel result = new CheckBoxListModel();
        addModelPath(String.format("/user/%s/.staging", user), result, true, true);
        addModelPath(String.format("/user/%s/.sparkStaging", user), result, true, true);
        addModelPath("/tmp", result, false, true);
        return result;
    }

    private void addModelPath(String stringPath, CheckBoxListModel model, boolean checked, boolean ignoreErrors) {
        Path path = new Path(stringPath);
        try {
            if(this.connection.exists(path)) {
                FileStatus status = this.connection.status(path);
                if(status.isFile()) {
                    error(ignoreErrors, stringPath + " is not a directory");
                } else{
                    if(!paths.contains(stringPath)) {
                        model.addCheckBoxElement(path, checked);
                        paths.add(stringPath);
                    }
                }
            } else {
                error(ignoreErrors, "Directory does not exist");
            }
        } catch (FsException ex) {
            error(ignoreErrors, ex.getMessage());
        }
    }

    private void error(boolean ignore, String message) {
        if(!ignore) {
            ViewUtils.error(this.panel, message);
        }
    }

    public Result showDialog() {
        setVisible(true);
        return result;
    }

    public static class Result {

        private final List<Path> paths;

        private final Date date;

        public Result(List<Path> paths, Date date) {
            this.paths = paths;
            this.date = date;
        }

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

        public Date getDate() {
            return date;
        }

        @Override
        public String toString() {
            return new ToStringBuilder(this)
                    .append("paths", paths)
                    .append("date", date)
                    .toString();
        }
    }
} public class Progress {

    private Supplier<String> supplier;

    public String getValue() {
        return supplier != null ? supplier.get() : null;
    }

    public Supplier<String> getSupplier() {
        return supplier;
    }

    public void setSupplier(Supplier<String> supplier) {
        this.supplier = supplier;
    }
} public class FileItem {

    private final FileStatus status;

    private String name;

    public static FileItem parent() {
        FileItem res = new FileItem();
        res.name = "..";
        return res;
    }

    public FileItem(FileStatus status) {
        this.status = status;
    }

    private FileItem() {
        this.status = null;
    }

    public FileStatus getStatus() {
        return status;
    }

    public String getName() {
        return status != null ? status.getPath().getName() : name;
    }

    public FileSize getSize() {
        return status != null && status.isFile() ? new FileSize(status.getLen()) : null;
    }

    public String getOwner() {
        return status != null ? status.getOwner() : null;
    }

    public String getGroup() {
        return status != null ? status.getGroup() : null;
    }

    public Long getBlockSize() {
        return status != null ? status.getBlockSize() : null;
    }

    public FileTimestamp getModificationTime() {
        return status != null ? new FileTimestamp(status.getModificationTime()) : null;
    }

    public FileTimestamp getAccessTime() {
        return status != null ? new FileTimestamp(status.getAccessTime()) : null;
    }

    public String getPermissions() {
        return status != null ? status.getPermission().toString() : null;
    }

    public Icon getIcon() {
        return IconFactory.getIcon(getIconName());
    }

    public boolean isFile() {
        return status != null && status.isFile();
    }

    public Path getPath() {
        return status != null ? status.getPath() : null;
    }

    public boolean isDirectory() {
        return status != null && status.isDirectory();
    }

    private String getIconName() {
        if(status == null) return "folder-open";
        if(status.isDirectory()) {
            if(StringUtils.endsWith(getName(), ".har")) {
                return "har";
            } else {
                return "folder";
            }
        } else {
            return "file";
        }
    }

    public static class FileTimestamp implements Comparable<FileTimestamp> {

        private final long timestamp;

        public FileTimestamp(long timestamp) {
            this.timestamp = timestamp;
        }

        public long getTimestamp() {
            return timestamp;
        }

        @Override
        public String toString() {
            return DateFormatUtils.format(timestamp, "dd-MM-yyyy HH:mm:ss");
        }

        @Override
        public int compareTo(FileTimestamp other) {
            return Longs.compare(this.timestamp, other.timestamp);
        }
    }

    public static class FileSize implements Comparable<FileSize> {

        private final long size;

        public FileSize(long size) {
            this.size = size;
        }

        public long getSize() {
            return size;
        }

        @Override
        public String toString() {
            return FileSystemUtils.formatByteCount(size);
        }

        @Override
        public int compareTo(FileSize other) {
            return Longs.compare(this.size, other.size);
        }
    }
} 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 FileContentDialog extends WebDialog {

    private final CountingInputStream cstream;

    private final BufferedReader reader;

    private WebTextArea text;

    private WebStatusBar statusBar;

    private WebLabel statusLabel;

    private char[] buffer = new char[10000];

    private boolean hasMore = true;

    private final long bytesTotal;

    public FileContentDialog(InputStream stream, long bytesTotal, Window owner) {
        super(owner);
        this.cstream = new CountingInputStream(stream);
        this.reader = createReader(cstream);
        this.bytesTotal = bytesTotal;
        initView();
    }

    private void initView() {
        setModal(false);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setMinimumSize(new Dimension(600, 600));
        setLayout(new BorderLayout());
        setTitle("File content");
        setResizable(true);
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent event) {
                IOUtils.closeQuietly(reader);
            }
        });

        this.text = new WebTextArea();
        this.text.setText("");
        this.text.setLineWrap(true);
        this.text.setWrapStyleWord(false);
        this.text.setEditable(false);
        this.text.setFont(new Font("Consolas", Font.PLAIN, 12));
        this.statusLabel = new WebLabel("");
        this.statusBar = new WebStatusBar();
        this.statusBar.add(this.statusLabel);

        WebScrollPane scroll = new WebScrollPane(this.text);
        scroll.setDrawFocus(false);
        scroll.getVerticalScrollBar().addAdjustmentListener(this::onScrollBottom);
        scroll.getVerticalScrollBar().setUnitIncrement(100);

        this.add(scroll, BorderLayout.CENTER);
        this.add(this.statusBar, BorderLayout.PAGE_END);
        pack();
        setLocationRelativeTo(getOwner());
    }

    private void readChunk() {
        if(!hasMore) return;
        try {
            int read = reader.read(this.buffer, 0, 10000);
            if(read <= 0) {
                hasMore = false;
                return;
            }
            String more = new String(this.buffer, 0, read);
            this.text.append(more);
            this.statusLabel.setText(getByteInfo());
        } catch (IOException e) {
            ViewUtils.error(this.text, e.getMessage());
        }
    }

    private String getByteInfo() {
        return String.format("%s / %s read", formatByteCount(cstream.getByteCount()), formatByteCount(bytesTotal));
    }

    private BufferedReader createReader(InputStream stream) {
        InputStreamReader isr = new InputStreamReader(stream, StandardCharsets.UTF_8);
        return new BufferedReader(isr);
    }

    public void showDialog() {
        setVisible(true);
        readChunk();
    }

    private void onScrollBottom(AdjustmentEvent event) {
        if(!event.getValueIsAdjusting()) {
            Adjustable adjustable = event.getAdjustable();
            int position = event.getValue() + adjustable.getVisibleAmount();
            int bottom = adjustable.getMaximum() - 5;
            if(position >= bottom) {
                readChunk();
            }
        }
    }

} 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 PropertiesDialog extends WebDialog {

    private final PropertyTableModel model;

    public PropertiesDialog(Map<String, String> props, Window owner) {
        super(owner);
        this.model = new PropertyTableModel(props);
        initView();
        setLocationRelativeTo(owner);
    }

    private void initView() {
        setModal(false);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setSize(new Dimension(600, 600));
        setTitle("Properties");

        WebTable table = new WebTable(model);
        WebScrollPane scroll = new WebScrollPane(table);
        add(scroll);
    }
}
import com.alee.extended.filechooser.WebDirectoryChooser;
import com.alee.laf.filechooser.WebFileChooser;
import com.alee.laf.optionpane.WebOptionPane;
import com.alee.utils.SwingUtils;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.Path;
import pl.sdadas.fsbrowser.app.BeanFactory;
import pl.sdadas.fsbrowser.app.clipboard.ClipboardAction;
import pl.sdadas.fsbrowser.app.clipboard.ClipboardHelper;
import pl.sdadas.fsbrowser.exception.FsException;
import pl.sdadas.fsbrowser.fs.connection.ConnectionConfig;
import pl.sdadas.fsbrowser.fs.connection.FsConnection;
import pl.sdadas.fsbrowser.fs.connection.HarFsConnection;
import pl.sdadas.fsbrowser.utils.FileSystemUtils;
import pl.sdadas.fsbrowser.utils.ViewUtils;
import pl.sdadas.fsbrowser.view.chmod.ChmodDialog;
import pl.sdadas.fsbrowser.view.chown.ChownDialog;
import pl.sdadas.fsbrowser.view.cleanup.CleanupDialog;
import pl.sdadas.fsbrowser.view.common.loading.Progress;
import pl.sdadas.fsbrowser.view.filebrowser.FileItem;
import pl.sdadas.fsbrowser.view.filebrowser.FileSystemTableModel;
import pl.sdadas.fsbrowser.view.filecontent.FileContentDialog;
import pl.sdadas.fsbrowser.view.mainwindow.MainPanel;
import pl.sdadas.fsbrowser.view.props.PropertiesDialog;
import java.awt.*;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
    }

    public FileAction moveToTrashAction() {
        return FileAction.builder((items) -> doRemove(items, false))
                .name("Move to trash")
                .icon("item-bin")
                .get();
    }

    private void doRemove(List<FileItem> selection, boolean skipTrash) {
        ViewUtils.handleErrors(parent, () -> {
            Path[] paths = getItemPaths(selection);
            if(paths.length == 0) return;
            String format = skipTrash ? "delete <b>%s</b>?" : "move <b>%s</b> to trash?</html>";
            String message = "<html>Do you want to " + String.format(format, getPathsName(paths));
            Window window = SwingUtils.getWindowAncestor(parent);
            int result = WebOptionPane.showConfirmDialog(window, message, "Confirm",
                    WebOptionPane.YES_NO_OPTION, WebOptionPane.QUESTION_MESSAGE);
            if(result == WebOptionPane.YES_OPTION) {
                parent.getConnection().remove(paths, skipTrash);
                parent.getModel().reloadView();
            }
        });
    }

    public FileAction mkdirAction() {
        return FileAction.builder(this::doMkdir)
                .name("Create directory (mkdir)")
                .icon("folder-new")
                .get();
    }

    private void doMkdir(List<FileItem> selection) {
        ViewUtils.handleErrors(parent, () -> {
            Window window = SwingUtils.getWindowAncestor(parent);
            String value = WebOptionPane.showInputDialog(window, "Directory name", "Create directory",
                    WebOptionPane.QUESTION_MESSAGE);
            if(StringUtils.isNotBlank(value)) {
                Path path = new Path(parent.getModel().getCurrentPath(), value);
                parent.getConnection().mkdir(path);
                parent.getModel().reloadView();
            }
        });
    }

    public FileAction touchAction() {
        return FileAction.builder(this::doTouch)
                .name("Create file (touch)")
                .icon("file-new")
                .get();
    }

    private void doTouch(List<FileItem> selection) {
        ViewUtils.handleErrors(parent, () -> {
            Window window = SwingUtils.getWindowAncestor(parent);
            String value = WebOptionPane.showInputDialog(window, "File name", "Create file",
                    WebOptionPane.QUESTION_MESSAGE);
            if(StringUtils.isNotBlank(value)) {
                Path path = new Path(parent.getModel().getCurrentPath(), value);
                parent.getConnection().touch(path);
                parent.getModel().reloadView();
            }
        });
    }

    public FileAction copyAction() {
        return FileAction.builder(this::doCopy)
                .name("Copy")
                .icon("copy")
                .predicates(this::notEmptyPredicate)
                .get();
    }

    public void doCopy(List<FileItem> selection) {
        ViewUtils.handleErrors(parent, () -> {
            Path[] paths = getItemPaths(selection);
            if(paths.length == 0) return;
            ClipboardHelper clipboard = parent.getClipboard();
            clipboard.copy(parent, Lists.newArrayList(paths));
        });
    }

    public FileAction cutAction() {
        return FileAction.builder(this::doCut)
                .name("Cut")
                .icon("cut")
                .predicates(this::notEmptyPredicate)
                .get();
    }

    public void doCut(List<FileItem> selection) {
        ViewUtils.handleErrors(parent, () -> {
            Path[] paths = getItemPaths(selection);
            if(paths.length == 0) return;
            ClipboardHelper clipboard = parent.getClipboard();
            clipboard.cut(parent, Lists.newArrayList(paths));
        });
    }

    public FileAction pasteAction() {
        return FileAction.builder(this::doPaste)
                .name("Paste")
                .icon("paste")
                .get();
    }

    public void doPaste(List<FileItem> selection) {
        ViewUtils.handleErrors(parent, () -> {
            ClipboardHelper clipboard = parent.getClipboard();
            if(clipboard.isEmpty()) return;
            ClipboardHelper.Paths paths = clipboard.getPaths();
            doPaste(paths);
            clipboard.clear();
        });
    }

    public void doPaste(ClipboardHelper.Paths paths) throws FsException {
        List<Path> src = paths.getPaths();
        Path dest = parent.getModel().getCurrentPath();
        if(FileSystemUtils.isSameFileSystem(paths.getConnection(), parent.getConnection())) {
            FsConnection conn = parent.getConnection();