Java File list same order like Window explorer
Asked Answered
F

1

2

I am using the code below to get file list ordering: (like window explorer)

    package com.codnix.quickpdfgenerator.testing;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.List;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;

    public class FileListOrder {
        public static void main(String args[]) {
            //huge test data set ;)

            File folder = new File("C:\\Users\\Codnix\\Desktop\\Test Sequence");
            File[] listOfFiles = folder.listFiles();
            List<File> filenames = Arrays.asList(listOfFiles); 

            //adaptor for comparing files
            Collections.sort(filenames, new Comparator<File>() {
                private final Comparator<String> NATURAL_SORT = new WindowsExplorerComparator();

                @Override
                public int compare(File o1, File o2) {;
                    return NATURAL_SORT.compare(o1.getName(), o2.getName());
                }
            });

            for (File f : filenames) {
                System.out.println(f);
            }
        }

        public static class WindowsExplorerComparator implements Comparator<String> {

            private static final Pattern splitPattern = Pattern.compile("\\d+|\\.|\\s");

            @Override
            public int compare(String str1, String str2) {
                Iterator<String> i1 = splitStringPreserveDelimiter(str1).iterator();
                Iterator<String> i2 = splitStringPreserveDelimiter(str2).iterator();
                while (true) {
                    //Til here all is equal.
                    if (!i1.hasNext() && !i2.hasNext()) {
                        return 0;
                    }
                    //first has no more parts -> comes first
                    if (!i1.hasNext() && i2.hasNext()) {
                        return -1;
                    }
                    //first has more parts than i2 -> comes after
                    if (i1.hasNext() && !i2.hasNext()) {
                        return 1;
                    }

                    String data1 = i1.next();
                    String data2 = i2.next();
                    int result;
                    try {
                        //If both datas are numbers, then compare numbers
                        result = Long.compare(Long.valueOf(data1), Long.valueOf(data2));
                        //If numbers are equal than longer comes first
                        if (result == 0) {
                            result = -Integer.compare(data1.length(), data2.length());
                        }
                    } catch (NumberFormatException ex) {
                        //compare text case insensitive
                        result = data1.compareToIgnoreCase(data2);
                    }

                    if (result != 0) {
                        return result;
                    }
                }
            }

            private List<String> splitStringPreserveDelimiter(String str) {
                Matcher matcher = splitPattern.matcher(str);
                List<String> list = new ArrayList<String>();
                int pos = 0;
                while (matcher.find()) {
                    list.add(str.substring(pos, matcher.start()));
                    list.add(matcher.group());
                    pos = matcher.end();
                }
                list.add(str.substring(pos));
                return list;
            }
        } 
    }

BUT, output when I run the program:

C:\Users\Codnix\Desktop\Test Sequence\1 test -12.jpg
C:\Users\Codnix\Desktop\Test Sequence\1 test --11.jpg
C:\Users\Codnix\Desktop\Test Sequence\1 test ---10.jpg

Expected Output (Like window explorer):

C:\Users\Codnix\Desktop\Test Sequence\1 test ---10.jpg
C:\Users\Codnix\Desktop\Test Sequence\1 test --11.jpg
C:\Users\Codnix\Desktop\Test Sequence\1 test -12.jpg

in window explorer it showing like this(sort by name)

What to do to get file list like this?

UPDATED

Implemented solution provided by @jannis

And here its output

before

1 test ---10.jpg
1 test --11.jpg
1 test -12.jpg
1.jpg
10.jpg
2.jpg

After (output)

1.jpg
1 test ---10.jpg
1 test --11.jpg
1 test -12.jpg
2.jpg
10.jpg

Expected

enter image description here

Fluctuate answered 6/2, 2020 at 10:12 Comment(5)
why can't you just use file1.getName().compareTo(file2.getName());?Loony
Sorting by name in Windows is non-trivial and is far more complicated than in your implementation. It's also configurable and version dependent.Illicit
According to this answer Windows uses StrCmpLogicalW for sorting files by name. You could try to implement your comparator by calling this system function using JNI.Illicit
@Illicit can you give little hit how to use it..Fluctuate
'll try to give you an example later.Illicit
I
5

Sorting by name in Windows is tricky and far more complicated than your implementation. It's also configurable and version dependent.

NOTE: I created a demo for what follows in this post. Check it out on GitHub.

Sorting file names using StrCmpLogicalWComparator function

According to some (e.g. here) Windows uses StrCmpLogicalW for sorting files by name.

You could try to implement your comparator by calling this system function using JNA (don't forget to include JNA library in your project):

Comparator:

public class StrCmpLogicalWComparator implements Comparator<String> {

    @Override
    public int compare(String o1, String o2) {
        return Shlwapi.INSTANCE.StrCmpLogicalW(
            new WString(o1), new WString(o2));
    }
}

JNA part:

import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;

public interface Shlwapi extends StdCallLibrary {

    Shlwapi INSTANCE = Native.load("Shlwapi", Shlwapi.class);

    int StrCmpLogicalW(WString psz1, WString psz2);
}

Handling file names that contain digits

I mentioned earlier that the way that Windows Explorer sorts files is configurable. You can change how numbers in file names are handled and toggle so-called "numerical sorting". You can read how to configure this here. Numerical sorting as explained in the docs:

Treat digits as numbers during sorting, for example, sort "2" before "10".

-- https://learn.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringex#SORT_DIGITSASNUMBERS

With numerical sorting enabled the result is:

file names sorted with numerical sorting enabled

whereas with numerical sorting disabled it looks like this:

file names sorted with numerical sorting disabled

This makes me think that Windows Explorer in fact uses CompareStringEx function for sorting which can be parameterized to enable this feature.

Sorting file names using CompareStringEx function

JNA part:

import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;

public interface Kernel32 extends StdCallLibrary {

    Kernel32 INSTANCE = Native.load("Kernel32", Kernel32.class);
    WString INVARIANT_LOCALE = new WString("");

    int CompareStringEx(WString lpLocaleName,
                        int dwCmpFlags,
                        WString lpString1,
                        int cchCount1,
                        WString lpString2,
                        int cchCount2,
                        Pointer lpVersionInformation,
                        Pointer lpReserved,
                        int lParam);

    default int CompareStringEx(int dwCmpFlags,
                                String str1,
                                String str2) {
        return Kernel32.INSTANCE
            .CompareStringEx(
                INVARIANT_LOCALE,
                dwCmpFlags,
                new WString(str1),
                str1.length(),
                new WString(str2),
                str2.length(),
                Pointer.NULL,
                Pointer.NULL,
                0);
    }
}

Numeric sort comparator:

public class CompareStringExNumericComparator implements Comparator<String> {

    private static int SORT_DIGITSASNUMBERS = 0x00000008;

    @Override
    public int compare(String o1, String o2) {
        int compareStringExComparisonResult =
            Kernel32.INSTANCE.CompareStringEx(SORT_DIGITSASNUMBERS, o1, o2);

        // CompareStringEx returns 1, 2, 3 respectively instead of -1, 0, 1
        return compareStringExComparisonResult - 2;
    }
}

Non-numeric sort comparator:

public class CompareStringExNonNumericComparator implements Comparator<String> {

    private static String INVARIANT_LOCALE = "";
    private static int NO_OPTIONS = 0;

    @Override
    public int compare(String o1, String o2) {
        int compareStringExComparisonResult =
            Kernel32.INSTANCE.CompareStringEx(NO_OPTIONS, o1, o2);

        // CompareStringEx returns 1, 2, 3 respectively instead of -1, 0, 1
        return compareStringExComparisonResult - 2;
    }
}

References

Illicit answered 6/2, 2020 at 16:45 Comment(14)
This is i need to add in pom net.java.dev.jna right?Fluctuate
yep @ButaniVijayIllicit
sorry for too much questions!. but i am getting com.sun.jna.win32.StdCallLibrary not exist ...i added dependency in pom and build code...may be build issue from my sideFluctuate
Failed to collect dependencies for [net.java.dev.jna:jna:jar:5.5.0 (compile)Fluctuate
Try running my example with ./mvnw testIllicit
will your example open in netbean?Fluctuate
@ButaniVijay I have no idea, but most probably there is a way to import a Maven project to Netbeans, but you're on your own here.Illicit
check my updated question.....may be need to workout more to achieve like windowFluctuate
@ButaniVijay I've updated the answer and the example. I fixed my JNA proxy:StrCmpLogicalW takes wide strings (JNA's com.sun.jna.WString) as parameters and in the previous solution I was using ordinary Strings which by default are not converted to WStrings by JNA unless you specify an additional parameter to Native.load method (Native.load("Shlwapi", Shlwapi.class, W32APIOptions.UNICODE_OPTIONS) - when loading the library like that even an ordinary String will do).Illicit
@ButaniVijay if this answer has solved your question please consider accepting it by clicking the check-mark. This indicates to the wider community that you've found a solution and gives some reputation to both the answerer and yourself. There is no obligation to do this.Illicit
Hey buddy thanks for everything.....so i am going to use NumericalCompareStringExComparator for sorting like window , is it right? I am appreciate your help and would like to donate !!!Let me know how can i help for your cup of coffee?Fluctuate
@ButaniVijay :) I'm glad I could be of help.Illicit
NumericalCompareStringExComparator is it final one window explorer using right?Fluctuate
Like I wrote it depends on the "numerical sort" setting. Sorting using CompareStringExNumericComparator acts as if "numerical sort" was enabled and CompareStringExNonNumericComparator otherwise.Illicit

© 2022 - 2024 — McMap. All rights reserved.