Reputation: 189626
I'm stumped... Let's say I have this directory tree:
{someRoot}/
{someRoot}/bar/
{someRoot}/bar/file1.txt
{someRoot}/foo/
{someRoot}/foo/baz/
{someRoot}/foo/baz/file3.txt
{someRoot}/foo/abracadabra.txt
{someRoot}/foo/file2.txt
{someRoot}/aardvark.txt
{someRoot}/food.txt
{someRoot}/zebra.txt
You'll note the ordering. Call this order1. At each stage, the directories come first before the files. (NOTE: bar/file1.txt
comes before foo
, so on a global basis, the directories do not all come before all the files.)
If I enumerate this directory tree, and then recursively enumerate the subdirectories, I'll get the following List<File>
, with ordering order2.
{someRoot}/
{someRoot}/aardvark.txt
{someRoot}/bar/
{someRoot}/foo/
{someRoot}/food.txt
{someRoot}/zebra.txt
{someRoot}/bar/file1.txt
{someRoot}/foo/abracadabra.txt
{someRoot}/foo/baz/
{someRoot}/foo/file2.txt
{someRoot}/foo/baz/file3.txt
If I create the straightforward Comparator<File>
:
Comparator<File> fc = new Comparator<File>(){
@Override public int compare(File o1, File o2) {
return o1.compareTo(o2);
}
};
and I sort, I get this ordering (order3) from lexicographic ordering:
{someRoot}
{someRoot}/aardvark.txt
{someRoot}/bar
{someRoot}/bar/file1.txt
{someRoot}/foo
{someRoot}/food.txt
{someRoot}/foo/abracadabra.txt
{someRoot}/foo/baz
{someRoot}/foo/baz/file3.txt
{someRoot}/foo/file2.txt
{someRoot}/zebra.txt
But I don't want this ordering (which has problems: note that food.txt
comes between directory foo
and its sub-items), I want order1. How can I write a Comparator to get me that?
Upvotes: 2
Views: 6815
Reputation: 43
Of course too late. In any case here is a Comparator
implementation that sorts as asked for in this SO.
public class DirectoryBeforeFileComparator implements Comparator<File> {
@Override
public int compare(File o1, File o2) {
if (o1.isDirectory() && !o2.isDirectory()) {
// directory before non-directory.
return -1;
}
if (!o1.isDirectory() && o2.isDirectory()) {
// non-directory after directory
return 1;
}
// compare two pathnames lexicographically
return o1.compareTo(o2);
}
}
Upvotes: 0
Reputation: 11017
This works in my tests.
new Comparator<File>() {
@Override
public int compare(File first, File second) {
if (first.isDirectory() && second.isDirectory())
return first.compareTo(second);
if (first.isDirectory())
return this.compareToFile(first, second);
if (second.isDirectory())
return -(this.compareToFile(second, first));
return this.compareFiles(first, second);
}
private int compareFiles(File first, File second) {
File firstParentFile = first.getParentFile();
File secondParentFile = second.getParentFile();
if (isSubDir(firstParentFile, secondParentFile))
return -1;
if (isSubDir(secondParentFile, firstParentFile))
return 1;
return first.compareTo(second);
}
private int compareToFile(File directory, File file) {
File fileParent = file.getParentFile();
if (directory.equals(fileParent))
return -1;
if (isSubDir(directory, fileParent))
return -1;
return directory.compareTo(file);
}
private boolean isSubDir(File directory, File subDir) {
for (File parentDir = directory.getParentFile(); parentDir != null; parentDir = parentDir.getParentFile()) {
if (subDir.equals(parentDir)) {
return true;
}
}
return false;
}
Upvotes: 4
Reputation: 189626
I did find a way to implement the pseudo-opposite of what I wanted (directories after files, haven't checked the edge cases where files and directory names are at the root level).
...And no, it's not trivial to transform this into what I do want, because I'm using lexicographic ordering of directories, which couples the ordering of directories at different levels and the ordering of directories at the same level.
static class FileDirectoryPriorityComparator implements Comparator<File>
{
@Override public int compare(File f1, File f2) {
if (f1.isDirectory())
{
if (f2.isDirectory())
{
return f1.compareTo(f2);
}
else
{
return compareDirFile(f1, f2);
}
}
else
{
if (f2.isDirectory())
{
return -compareDirFile(f2, f1);
}
// f1 and f2 are both files
else
{
return compareFiles(f1, f2);
}
}
}
private int compareDirFile(File dir, File file) {
/*
* If dir compares differently to file's parent, use that ordering.
* Otherwise, dir comes before file.
*/
File fparent = file.getParentFile();
if (fparent == null)
return -1;
int i = dir.compareTo(fparent);
if (i != 0)
return i;
return -1;
}
private int compareFiles(File f1, File f2) {
/*
* If f1's parent compares differently to f2's parent, use that ordering.
* Otherwise use default ordering.
*/
File fp1 = f1.getParentFile();
File fp2 = f2.getParentFile();
if (fp1 == null)
{
if (fp2 != null)
{
return 1;
}
}
else
{
if (fp2 == null)
{
return -1;
}
else
{
int i = fp1.compareTo(fp2);
if (i != 0)
return i;
}
}
return f1.compareTo(f2);
}
}
Upvotes: 0
Reputation: 120771
Edit You have to compare the Base Directory at first level and File name at second level
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
public class FileComparator implements Comparator<File> {
@Override
public int compare(File o1, File o2) {
int pathCompare = this.getPath(o1).compareTo(this.getPath(o2));
if (pathCompare != 0) {
return subPathCompare;
} else {
if (o1.isDirectory() && !o2.isDirectory()) {
return -1;
} else if (!o1.isDirectory() && o2.isDirectory()) {
return 1;
} else {
return o1.compareTo(o2);
}
}
}
//maybe there is a better way to speparete path and file name, but it works
private String getPath(File file) {
if (file.isFile()) {
return file.getParent().toLowerCase();
} else {
return file.getPath().toLowerCase();
}
}
}
If the separation of path and file is nessesary, because if one only do:
if (o1.isDirectory() && !o2.isDirectory()) {
return -1;
} else if (!o1.isDirectory() && o2.isDirectory()) {
return 1;
} else {
return o1.compareTo(o2);
}
the the result would be not case 1, but an other case where all directorys would be the first items, and the files are the last:
\dir1\
\dir2\
\dir3\
\dir4\
\dir1\file1\
\dir1\file2\
\dir2\file1\
\dir2\file2\
So my solution is to check first if the two compared files belong to the same directory.
Upvotes: 0
Reputation: 43504
This recursivly gets the sorted file tree as you like:
public static void main(String[] args) {
List<File> files = getFileTree(new File("."));
for (File f : files)
System.out.println(f);
}
private static List<File> getFileTree(File file) {
List<File> files = new LinkedList<File>();
files.add(file);
if (file.isDirectory()) {
File[] current = file.listFiles();
Arrays.sort(current, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if (o1.isDirectory())
return o2.isDirectory() ? o1.compareTo(o2) : -1;
else if (o2.isDirectory())
return 1;
return o1.compareTo(o2);
}
});
for (File f : current)
files.addAll(getFileTree(f));
}
return files;
}
Upvotes: 3
Reputation: 6004
Comparator<File> fc = new Comparator<File>(){
@Override public int compare(File o1, File o2) {
boolean isFirstDirectory = o1.isDirectory();
boolean isSecondDirectory = o2.isDirectory();
if(!isFirstDirectory && !isSecondDirectory) {
return o1.compareTo(o2);
} else if (isFirstDirectory && !isSecondDirectory){
return (int) 1;
} else if(isSecondDirectory && !isFirstDirectory) {
return -1;
} else {
return o1.compareTo(o2);
}
}
};
Upvotes: 0