Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrieving resources for a subpackage class

I haven't been working with java for long, so I'm not sure as what else to look for. I hope somebody can point me in the right direction.

Goal: I want to use a look up table, stored as a text file. But I don't want to use absolute paths, as in the end I'd like to pack a release and be able to call it from any location (the text file will be included int the packed release).

Current setup: I put the text file in a folder called "resources" (because from reading tutorials about java, I got the impression, this is where I'm supposed to put it to maintain a better structured project).

In the root package folder I have a class (MainClass.java) that is calling another class (LookUpClass.java) in a subpackage. The folder setup is as followed:

  • src
    • java
      • main.package.com
        • subpackage
          • LookUpClass.java
          • PlotterClass.java
        • MainClass.java
    • resources
      • LookUpTables
        • LookUpTable1.txt
        • LookUpTable2.txt

I wrote a method in LookUpClass.java that is retrieving a certain line from my lookup tables in resources. To retrieve the file and read out a certain line, I used

// Gets respective line from LUT
private static String getLineFromLUT(int line) {
    URL url = LookUpClass.class.getClass().getResource("/LookUpTables/LookUpTable1.txt");
    File file = new File(url.toURI());
    BufferedReader br = new BufferedReader(new FileReader(file));

    for (int i = 0; i < line; ++i)
        br.readLine();

    return br.readLine;
}

In my project structure the "java" folder is marked as "source", while "resources" is marked as, well, "resources".

My test setup is very simple:

public static void main(String[] args) throws URISyntaxException, IOException {
    String c = LookUpClass.getLineFromLUT(5);
    System.out.println("Color of line 5: " + c);
}

Output:

Color of line 5: 0  0   38

(Which is correct.)

I added the exact same lines to PlotterClass.java and it works fine, too.

Problem:

Now, If I try the same in MainClass.java I get an error with url being null. It seems the resource/resource folder can't be found.

I read through various postings on SO already and tried out several proposed solutions, which all failed so far:

  • If using LookUpClass.class.getClassLoader().getResource("/LookUpTables/LookUpTable1.txt") both callings from MainClass.java and LookUpClass.java fail (url is null).
  • I tried using following paths (all not working in either of the classes): "LookUpTables/LookUpTable1.txt" (removing starting "/") "/subpackage/LookUpTables/LookUpTable1.txt" "../subpackage/LookUpTables/LookUpTable1.txt"
  • Since using Idea IntelliJ, I checked "Settings > Build, Execution, Deployment > Compiter > Resource patterns" and added "*.txt" to the patterns. Nothing changed.
  • If adding Class c = LookUpClass.class.getClass();, in Debug mode c is "class.java.lang.Class". I was expecting something like "main.package.com.subpackage.LookUpClass".
  • At some point I tried using getResourceAsStream(), but I didn't understand how to get my (e.g.) 5th line, so I discarded it. I'm willing to read up on this, if it solves my problem though.

I have no idea how to solve this problem. And I realize that at this point I'm just trying out things, not even understanding why it could or could not work. For me, it just seems LookUpClass.java is run from a different location than MainClass.java. But the "resources"-folder and respective text file location never change. How can the file be found in one case, but not in the other?

like image 571
fukiburi Avatar asked Apr 02 '15 13:04

fukiburi


People also ask

How do I get all classes in a classpath?

You can get all classpath roots by passing an empty String into ClassLoader#getResources() . Enumeration<URL> roots = classLoader. getResources("");

How can you directly access all classes in the packages in Java?

1) Using packagename.* If you use package.* then all the classes and interfaces of this package will be accessible but not subpackages. The import keyword is used to make the classes and interface of another package accessible to the current package.

How to add class in a package?

Adding a class to a Package : We can add more classes to a created package by using package name at the top of the program and saving it in the package directory. We need a new java file to define a public class, otherwise we can add the new class to an existing . java file and recompile it.


1 Answers

Maven has a standard directory layout. The directory src/main/resources is intended for such application resources. Place your text files into it.

You now basically have two options where exactly to place your files:

  1. The resource file belongs to a class.

An example for this is a class representing a GUI element (a panel) that needs to also show some images.

In this case place the resource file into the same directory (package) as the corresponding class. E.g. for a class named your.pkg.YourClass place the resource file into the directory your/pkg:

src/main
 +-- java/
 |    +-- your/pkg/
 |    |    +-- YourClass.java
 +-- resources/
      +-- your/pkg/
           +-- resource-file.txt

You now load the resource via the corresponding class. Inside the class your.pkg.YourClass you have the following code snippet for loading:

String resource = "resource-file.txt"; // the "file name" without any package or directory
Class<?> clazz = this.getClass(); // or YourClass.class
URL resourceUrl = clazz.getResource(resource);
if (resourceUrl != null) {
    try (InputStream input = resourceUrl.openStream()) {
        // load the resource here from the input stream
    }
}

Note: You can also load the resource via the class' class loader:

String resource = "your/pkg/resource-file.txt";
ClassLoader loader = this.getClass().getClassLoader(); // or YourClass.class.getClassLoader()
URL resourceUrl = loader.getResource(resource);
if (resourceUrl != null) {
    try (InputStream input = resourceUrl.openStream()) {
        // load the resource here from the input stream
    }
}

Choose, what you find more convenient.

  1. The resource belongs to the application at whole.

In this case simply place the resource directly into the src/main/resources directory or into an appropriate sub directory. Let's look at an example with your lookup file:

src/main/resources/
    +-- LookupTables/
         +-- LookUpTable1.txt

You then must load the resource via a class loader, using either the current thread's context class loader or the application class loader (whatever is more appropriate - go and search for articles on this issue if interested). I will show you both ways:

String resource = "LookupTables/LookUpTable1.txt";
ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader();
ClassLoader sysLoader = ClassLoader.getSystemClassLoader();
URL resourceUrl = ctxLoader.getResource(resource); // or sysLoader.getResource(resource)
if (resourceUrl != null) {
    try (InputStream input = resourceUrl.openStream()) {
        // load the resource here from the input stream
    }
}

As a first suggestion, use the current thread's context class loader. In a standalone application this will be the system class loader or have the system class loader as a parent. (The distinction between these class loaders will become important for libraries that also load resources.)

You should always use a class loader for loading resource. This way you make loading independent from the place (just take care that the files are inside the class path when launching the application) and you can package the whole application into a JAR file which still finds the resources.

like image 180
Seelenvirtuose Avatar answered Sep 18 '22 23:09

Seelenvirtuose