0

I am working on a Java project, which runs fine on Windows 10, but when I tested it in Ubuntu, it shows

"AWT-EventQueue-0" java.lang.NullPointerException: Cannot read the array length because "allFiles" is null.

I read this answer, but could not find a fix.

What I am doing in the project is load an array of images from a certain path. Here is the faulty part of my code:

BufferedImage[] allImages;

public ImageArray(String set, int n) {
    File path = new File("res/mnist_png/" + set + "/" + n);
    File[] allFiles = path.listFiles();

    allImages = new BufferedImage[allFiles.length];

    JLabel label[] = new JLabel[allFiles.length];

    for (int i = 0; i < allFiles.length; i++) {
        try {
            allImages[i] = ImageIO.read(allFiles[i]);
            label[i] = new JLabel();
            ImageIcon icon = new ImageIcon(allImages[i]);

I tried removing the variable allFiles and replacing its use with the actual code it holds but with no success. I saw in the previously answered that the use of the new / this keywords could fix the issue, but I don't seem to be able to find if and where to use them. I printed the value of the allFiles and path.listFiles() and it is indeed null. Is there a way for the program to work if they remain null? Would changing the null somehow break their intended work?

As I mentioned, the problem occurs only on Linux, but works fine on Windows. Some help would be much appreciated.

gosho09
  • 11
  • 3
  • 4
    Hint: what does the documentation for `File.ListFiles()` say about the return value? – Jon Skeet May 20 '22 at 18:42
  • `The function returns an array of Files denoting the files in a given abstract pathname if the path name is a directory else returns null.` Printing `path` gives a deterministic value in both OS (res\mnist_png\Training\0). But printing `allFiles` gives `null` on Linux but an actual reference on Windows `([Ljava.io.File;@50a5b0d)`. `path` does in fact print a directory. Could the problem be definition of the term *directory* on the two OS? – gosho09 May 20 '22 at 18:59
  • 4
    It is not correct to try to list application resources. If you ever package your application as a .jar file, the File class won’t work anyway. As for your current problem, it’s likely due to your use of a relative file name, which relies on the [current working directory](https://en.wikipedia.org/wiki/Working_directory) of the Java process itself. – VGR May 20 '22 at 19:01
  • 3
    "printing allFiles gives null on Linux" - which suggests that there *isn't* a directory with that name (relative to the current working directory) on Linux. – Jon Skeet May 20 '22 at 19:02
  • From what I gather, the problem seems to be *how* I give directory path. There is actually a *res/mnist_png/...* folder in my project (I download an archive over the internet and extract the content which is these folders). I am using relative path to try to access but I also tried absolute path and `System.getProperty("user.home")` but to no avail. Is it a fixable problem I am overseeing, is it a structural design problem I have created? As to .jar files, the project works as .jar in Windows in any directory the project is put and does it's job. – gosho09 May 20 '22 at 19:27
  • Shot in the dark: change `"res/mnist_png/"` to `"./res/mnist_png/"`. – Old Dog Programmer May 20 '22 at 21:14
  • 1
    No, it’s not how you specify the directory path. Again, it’s the current working directory that matters, because you are using a relative file name (that is, a file name that does not start with the file separator). – VGR May 20 '22 at 22:24
  • 1
    Add this line to your code: `System.out.println ("Working directory is " + System.getProperty("user.dir"));` – Old Dog Programmer May 21 '22 at 02:51
  • The source tree isn't present at runtime. Resources get packaged into the JAR/WAR/EAR file, and that is where you should look for them, and how you should look for them is with `Class.getResource()` and friends. – user207421 May 21 '22 at 03:47
  • One reason such a program would work on Windows but not Linux is that file names and directory names on Windows are _not_ case sensitive. But, on Linux, they are. – Old Dog Programmer May 21 '22 at 22:00

1 Answers1

0

First problem: You are using a relative file name. Relative file names have a different meaning depending on the current working directory of the Java process. This is not a Java concept; each process in a system has had its own current directory since long before Java existed.

Second problem: You are trying to list application resources. If you ever choose to package your application as a .jar file, this will not work, because a .jar is a single archive file; the data inside it is all part of one file and they do not constitute actual files.

Relative file names

Any file that does not start with a directory separator (/ on most systems, or optionally \ in Windows) is a relative file name. The actual file location that relative file name refers to depends on the current directory of the Java process. All programs, not just Java programs, work this way.

Some examples:

File name                       Current directory       Actual file location
---------                       -----------------       --------------------

res/mnist_png/A/1/image01.png   /home/gosho09/project   /home/gosho09/project/res/mnist_png/A/1/image01.png
mnist_png                       /home/gosho09/project   /home/gosho09/project/mnist_png
mnist_png                       /                       /mnist_png

/home/gosho09/project/res       /tmp                    /home/gosho09/project/res
/home/gosho09/project/res       /home/gosho09           /home/gosho09/project/res
/home/gosho09/project/res       /usr/local/bin          /home/gosho09/project/res
/var/log                        /tmp                    /var/log
/var/log                        /home/gosho09           /var/log
/var/log                        /usr/local/bin          /var/log

As you can see, if the file name does not start with a /, it is relative, and the current directory determines the actual location.

If the file name starts with /, it is considered an absolute file name. Absolute file names are not affected by the current directory.

However… you should not use file names at all.

If you ever want to distribute your application, you will most likely want it to be packaged as a .jar file. A .jar file is a single archive file which contains compiled classes, and application resource files, like your image sets.

Because a .jar file is an archive (it’s actually a specialized zip file), the entries inside it are just parts of the archive, in compressed form. They are not individual files, just sequences of bytes. You cannot read them with the File class.

Fortunately, there is a way to read application resources, which will work both when your application is packaged as a .jar file, and when it exists as regular .class files and data files: the Class.getResource method.

A typical usage might look like this:

String path = "/mnist_png/" + set + "/" + n + "/image" + i + ".png");
URL imageLocation = ImageArray.class.getResource(path);

if (imageLocation == null) {
    throw new RuntimeException("Missing resource \"" + path + "\"");
}

allImages[i] = ImageIO.read(imageLocation);

You may be wondering how one is supposed to list files without the File class. The answer is: You can’t and you shouldn’t.

By design, application resources cannot be listed. This is because they are not guaranteed to be listable. Resources are loaded by ClassLoaders, and ClassLoaders may or may not read from directories or from .jar files.

(In fact, the Java SE runtime no longer includes its core classes as a .jar file; as a result, third party tools which used to assume those classes would be available as a .jar file had to be rewritten. Java did not pull the rug out from under those tools’ developers; it was never considered safe to assume classes would come from .jar files, and those developers chose not to heed that warning.)

The alternative to listing the resources is to include a resource which contains a list of the known resource paths. It’s your application; you know what’s in it. So just write a simple plain text listing, include it in your application, and read from that:

String root = "/mnist_png/" + set + "/" + n + "/";
String listingPath = root + "image-list.txt";

try (BufferedReader listing = new BufferedReader(
    new InputStreamReader(
        Objects.requireNonNull(
            ImageArray.class.getResourceAsStream(listingPath),
            "Missing resource \"" + listingPath + \""),
        StandardCharsets.UTF_8))) {

    List<JLabel> labelList = new ArrayList<>();

    String path;
    while ((path = listing.readLine()) != null) {
        URL imageLocation = ImageArray.class.getResource(root + path);
        if (imageLocation == null) {
            throw new RuntimeException(
                "Missing resource \"" + root + path + "\"");
        }
        labelList.add(new JLabel(new ImageIcon(imageLocation)));
    }

    labels = labelList.toArray(new JLabel[0]);
}
VGR
  • 40,506
  • 4
  • 48
  • 63