Home > Blockchain >  How to sort file\directory tree in lexicographic order (case-insensitive) on Linux system
How to sort file\directory tree in lexicographic order (case-insensitive) on Linux system

Time:12-13

I have a class that do a tree from directory, subdirectory and files. And need to satisfy two conditions. Sort the contents in following way: -Directories should go first. -Directories and files are sorted in lexicographic order (case-insensitive). I work on windows system and code below work fine, all is sorted how I want. Directories go first and are sorted in lexicographic order. But I read that on windows system is automatically sorted in lexicographic order. But this code doesn't work on Linux, the line children.sort(Comparator.comparing(file -> file.isDirectory() ? -1 : 1)); do not work and files\directory aren't sorted in lexicographic order (case-insensitive). How to solve problem with sort conditions on linux system? How can change sort conditions in code?

import static java.util.Comparator.comparing;

public class TreeNode<T> implements Iterable<TreeNode<T>> {

    public T data;
    public TreeNode<T> parent;
    public List<TreeNode<T>> children;

    public boolean isRoot() {
        return parent == null;
    }

    private List<TreeNode<T>> elementsIndex;

    public TreeNode(T data) {
        this.data = data;
        this.children = new LinkedList<TreeNode<T>>();
        this.elementsIndex = new LinkedList<TreeNode<T>>();
        this.elementsIndex.add(this);
    }

    public TreeNode<T> addChild(T child) {
        TreeNode<T> childNode = new TreeNode<T>(child);
        childNode.parent = this;
        this.children.add(childNode);
        this.registerChildForSearch(childNode);
        return childNode;

    }

    private void registerChildForSearch(TreeNode<T> node) {
        elementsIndex.add(node);
        if (parent != null)
            parent.registerChildForSearch(node);
    }

    @Override
    public String toString() {
        return data != null ? data.toString() : "[data null]";
    }

    @Override
    public Iterator<TreeNode<T>> iterator() {
        TreeNode<T> iter = new TreeNode<T>((T) this);
        return (Iterator<TreeNode<T>>) iter;
    }

    public static TreeNode<File> createDirTree(File folder) {
        if (!folder.isDirectory()) {
            throw new IllegalArgumentException("folder is not a Directory");
        }

       List<File> children = Arrays.asList(folder.listFiles());
children.sort(Comparator.comparing(file -> file.isDirectory() ? -1 : 1));

        TreeNode<File> DirRoot = new TreeNode<File>(folder);
for (File file : children) {
            if (file.isDirectory()) {
                appendDirTree(file, DirRoot);
            } else {
                appendFile(file, DirRoot);
            }
        }
        return DirRoot;
    }

    public static void appendDirTree(File folder, TreeNode<File> dirRoot) {
        dirRoot.addChild(folder);

        List<File> children = Arrays.asList(folder.listFiles());
        children.sort(comparing(file -> file.isDirectory() ? -1 : 1));
for (File file : children) {
            if (file.isDirectory()) {
                appendDirTree(file, dirRoot.children.get(dirRoot.children.size() - 1));
            } else {
                appendFile(file, dirRoot.children.get(dirRoot.children.size() - 1));
            }
        }
    }

    public static void appendFile(File file, TreeNode<File> filenode) {
        filenode.addChild(file);

    }


    public static String renderDirectoryTree(TreeNode<File> tree) {
        List<StringBuilder> lines = renderDirectoryTreeLines(tree);
        String newline = "\n";
        StringBuilder sb = new StringBuilder(lines.size() * 20);
        for (StringBuilder line : lines) {
            sb.append(line);
            sb.append(newline);
        }
        //System.out.println(sb);
        return sb.toString();
    }

    public static List<StringBuilder> renderDirectoryTreeLines(TreeNode<File> tree) {

        List<StringBuilder> result = new LinkedList<>();
        result.add(new StringBuilder().append(tree.data.getName()   " "   calculateFileSize(tree)   " bytes"));
        Iterator<TreeNode<File>> iterator = tree.children.iterator();
        while (iterator.hasNext()) {
            List<StringBuilder> subtree = renderDirectoryTreeLines(iterator.next());
            if (iterator.hasNext()) {
                addSubtree(result, subtree);
            } else {
                addLastSubtree(result, subtree);
            }
        }

        return result;
    }

    private static void addSubtree(List<StringBuilder> result, List<StringBuilder> subtree) {
        Iterator<StringBuilder> iterator = subtree.iterator();
        result.add(iterator.next().insert(0, "├─ "));
        while (iterator.hasNext()) {
            result.add(iterator.next().insert(0, "│  "));
        }
    }

    private static void addLastSubtree(List<StringBuilder> result, List<StringBuilder> subtree) {
        Iterator<StringBuilder> iterator = subtree.iterator();
        result.add(iterator.next().insert(0, "└─ "));
        while (iterator.hasNext()) {
            result.add(iterator.next().insert(0, "   "));
        }
    }

public static long calculateFileSize(TreeNode<File> tree) {
    long fileSize = 0;
    if (tree.data.isDirectory()) {
        List<TreeNode<File>> children = tree.children;


        for (TreeNode<File> child : children) {
            fileSize  = calculateFileSize(child);
        }

    } else {
        fileSize = tree.data.length();
    }
    return fileSize;
}

}

CodePudding user response:

A short version could look something like this.

Comparator<File> lexicographicFileComparator = Comparator.comparing(File::isFile)
    .thenComparing(Comparator.naturalOrder());

You've wrote that File.compareTo doesn't even check whether the file exists or not. I don't think it's the job of compareTo or Comparator to check if a file exists. Files that don't exist should be filtered before.


I think your example is overusing static methods. TreeNode should be an abstract class, while you have an implementation called FileTreeNode that replaces your static methods.

Here is an example

TreeNode

public abstract class TreeNode<N extends TreeNode<N, T>, T> implements Iterable<N> {

    private static <N extends TreeNode<N, ?>> Stream<CharSequence> renderBranch(N node) {
        Stream.Builder<CharSequence> result = Stream.builder();
        result.add(node.printNode());
        Iterator<N> iterator = node.getChildren().iterator();
        while (iterator.hasNext()) {
            Stream<CharSequence> subtree = renderBranch(iterator.next());
            Iterator<CharSequence> subtreeIterator = subtree.iterator();
            String branchSplit = "├─ ";
            String branchSpacer = "│  ";
            if (!iterator.hasNext()) {
                branchSplit = "└─ ";
                branchSpacer = "   ";
            }
            result.add(branchSplit   subtreeIterator.next());
            while (subtreeIterator.hasNext()) {
                result.add(branchSpacer   subtreeIterator.next());
            }
        }
        return result.build();
    }

    private final T data;
    private final N parent;

    private final List<N> children;

    public TreeNode(T data) {
        this(data, null);
    }

    protected TreeNode(T data, N parent) {
        this.data = data;
        this.parent = parent;
        children = initChildren();
    }

    /**
     * Called in constructor to initialize the children list.
     */
    protected abstract List<N> initChildren();

    /**
     * Used to avoid unsafe casting.
     *
     * @return This
     */
    protected abstract N instance();

    /**
     * TreeNode knows how to print the tree, but not how to print the current element. This way the child class can decide how it wants to be printed in the tree;
     *
     * @return readable text representation of node.
     * @see TreeNode#renderBranch()
     */
    protected abstract CharSequence printNode();

    /**
     * @return Returns a string representation of the entire branch starting at this node.
     */
    public String renderBranch() {
        Stream<CharSequence> lines = renderBranch(instance());
        return lines.collect(Collectors.joining("\n"));
    }

    /**
     * @return Returns a stream of the entire branch starting at this node
     */
    public Stream<N> streamBranch() {
        return Stream.concat(Stream.of(instance()), getChildren().stream().flatMap(TreeNode::streamBranch));
    }

    public T getData() {
        return data;
    }

    public N getParent() {
        return parent;
    }

    public List<N> getChildren() {
        return children;
    }

    public boolean isRoot() {
        return parent == null;
    }

    @Override
    public String toString() {
        return data != null ? data.toString() : "[data null]";
    }

    @Override
    public Iterator<N> iterator() {
        // No clue what you want to do here, but your method will not work.
        return children.iterator();
    }
}

FileTreeNode

public class FileTreeNode extends TreeNode<FileTreeNode, File> {

    public FileTreeNode(File root) {
        super(root);
    }

    protected FileTreeNode(File data, FileTreeNode parent) {
        super(data, parent);
    }

    public long calculateFileSize() {
        return streamBranch().mapToLong(value -> value.getData().length()).sum();
    }

    @Override
    protected CharSequence printNode() {
        return getData().getName()   " "   calculateFileSize()   " bytes";
    }

    @Override
    protected List<FileTreeNode> initChildren() {
        File file = getData();
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                return Arrays.stream(files)
                        .sorted(Comparator.comparing(File::isFile).thenComparing(Comparator.naturalOrder()))
                        .map(path -> new FileTreeNode(path, this))
                        .toList();
            }
        }
        return Collections.emptyList();
    }

    @Override
    protected FileTreeNode instance() {
        return this;
    }
}

Usage

File file = new File("C:\\dir");
FileTreeNode rootNode = new FileTreeNode(file);
System.out.println(rootNode.renderBranch());

This way you can easily implement a TreeNode structure for other classes too. For example File is outdated and replaced by Path so maybe you will want a PathTreeNode in the future. Who knows.

  • Related