Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sort List<File> to list directories first and grouping files by directory?

In order to get all files contained in a specified directory and according to some extensions, I'm using the method listFiles of class FileUtils from Apache Commons IO library, as in the following code sample.

ArrayList<String> wildcards = new ArrayList<>();
wildcards.add("*.cpp");
wildcards.add("*.h");
wildcards.add("*.txt");

File dir = new File("/path/to/dir");
Collection<File> found = FileUtils.listFiles(
        dir,
        new WildcardFileFilter(wildcards, IOCase.SENSITIVE),
        DirectoryFileFilter.DIRECTORY);

List<File> files = new ArrayList<>(found);

The order of items in the resulting Collection<File> varies in the different operating systems, so I would sort them (ie. the wrapping list files) in according to the following rules.

  • The directories should be listed before the files.
  • The sorting routine should group files by directory.

Example:

/path/to/dir/first/subpath/main.cpp
/path/to/dir/first/subpath/utils.cpp
/path/to/dir/first/subpath/utils.h
/path/to/dir/first/main.cpp
/path/to/dir/first/utils.cpp
/path/to/dir/first/utils.h
/path/to/dir/second/main.cpp
/path/to/dir/second/utils.cpp
/path/to/dir/second/utils.h
/path/to/dir/README.txt
like image 660
enzom83 Avatar asked Aug 31 '15 13:08

enzom83


People also ask

How do I get a list of all files and directories in a given directory in Python?

os. listdir() method gets the list of all files and directories in a specified directory. By default, it is the current directory.


1 Answers

After an entire afternoon of scratching my head at this, here's what worked for me:

files.sort((p1, p2) -> pathToStr(p1).compareToIgnoreCase(pathToStr(p2)));
files.forEach(System.out::println); //They'll be ordered now!

//A method I wrote for pulling this off
public static String pathToStr(File path) {
    boolean isDir = path.isDirectory();
    String str = path.getAbsolutePath().replace("\\", "/").replace("/", "/\0");
    if (!isDir) {
        int idx = str.lastIndexOf("/\0");
        str = str.substring(0, idx) + str.substring(idx, str.length()).replace("/\0", "/");
    }
    return str;
}

So basically, what pathToStr() does is making a string representation of a file path, but with a twist: all folder names in the path will have a null \0 character prepended. This way, the stock string ordering will push all subfolders to the top of each folder. It should work fine as long as you don't have any file names starting with a null char, which I don't think is even possible due to how good old C handles strings.

As an additional note, if you want to order things in a more Unix-like fashion, you can change that compareToIgnoreCase to just compareTo, in order to consider different casing.

This question may be kind of old (5 years old at the moment I'm writing this), but I couldn't find any answers that would not involve using libraries or mixing several comparators, so once I figured it out I decided to come back here and add my two cents. Enjoy your saved afternoon.

Edit: Since the question does not use NIO paths, I had to change my code to an alternative version using only File; but in case you're using NIO paths like me, here's the code I'm using:

List<Path> items = Files.walk(rootFolder).collect(Collectors.toList());
items.sort((p1, p2) -> pathToStr(p1).compareToIgnoreCase(pathToStr(p2)));
items.forEach(System.out::println); //They'll be ordered now!

public static String pathToStr(Path path) {
    boolean isDir = Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS);
    String str = path.toAbsolutePath().toString().replace("\\", "/").replace("/", "/\0");
    if (!isDir) {
        int idx = str.lastIndexOf("/\0");
        str = str.substring(0, idx) + str.substring(idx, str.length()).replace("/\0", "/");
    }
    return str;
}

There we go, whether you're using NIO or not, this answer has you covered.

like image 163
DragShot Avatar answered Oct 07 '22 09:10

DragShot