9

I have some Json files in the folder "resource/json/templates". I want to read these Json files. So far, the snippet of code below is allowing me to do so when running the program in IDE but it fails when I run it in the jar.

  JSONParser parser = new JSONParser();
  ClassLoader loader = getClass().getClassLoader();
  URL url = loader.getResource(templateDirectory);
  String path = url.getPath();
  File[] files = new File(path).listFiles();
  PipelineTemplateRepo pipelineTemplateRepo = new PipelineTemplateRepoImpl();
  File templateFile;
  JSONObject templateJson;
  PipelineTemplateVo templateFromFile;
  PipelineTemplateVo templateFromDB;
  String templateName;


  for (int i = 0; i < files.length; i++) {
    if (files[i].isFile()) {
      templateFile = files[i];
      templateJson = (JSONObject) parser.parse(new FileReader(templateFile));
      //Other logic
    }
  }
}
catch (Exception e) {
  e.printStackTrace();
}

Any help would be greatly appreciated.

Thanks a lot.

Juvenik
  • 900
  • 1
  • 8
  • 26
  • Please give more details. What is the error message? How did you create the jar file? – Marek J Dec 12 '17 at 14:57
  • Sorry, I created a jar by maven build. And the 'files' is coming as null. – Juvenik Dec 12 '17 at 15:04
  • The basic answer is, you can't. A Jar file is Zip file, unless you can identify the location of the Zip file which contains the resources in question at runtime, you can't list it's contents. You code should make no assumptions about the name or location of the Jar file, as it couples your code to a variable state. Instead, when you build/package the Jar, generate a file list and save it to a well known file/location and store this in the Jar file. At runtime, read this resource and then you will have a list of the other files – MadProgrammer Dec 12 '17 at 19:45
  • @Juvenik Does my code not work for you? Just let me know and I might be able to help. – xtratic Dec 13 '17 at 15:23

2 Answers2

8

Assuming that in the class path, in the jar the directory starts with /json (/resource is a root directory), it could be as such:

    URL url = getClass().getResource("/json");
    Path path = Paths.get(url.toURI());
    Files.walk(path, 5).forEach(p -> System.out.printf("- %s%n", p.toString()));

This uses a jar:file://... URL, and opens a virtual file system on it.

Inspect that the jar indeed uses that path.

Reading can be done as desired.

     BufferedReader in = Files.newBufferedReader(p, StandardCharsets.UTF_8);
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • 2
    Hi, I tried this but I am getting the following error: java.nio.file.FileSystemNotFoundException at com.sun.nio.zipfs.ZipFileSystemProvider.getFileSystem(ZipFileSystemProvider.java:171) at com.sun.nio.zipfs.ZipFileSystemProvider.getPath(ZipFileSystemProvider.java:157) at java.nio.file.Paths.get(Paths.java:143) – Juvenik Dec 13 '17 at 05:03
  • @Juvenik sorry, I had the hope that this would be easier than xtratic's solution; it looks so plausible. – Joop Eggen Dec 13 '17 at 08:04
2

Firstly, remember that Jars are Zip files so you can't get an individual File out of it without unzipping it. Zip files don't exactly have directories, so it's not as simple as getting the children of a directory.

This was a bit of a difficult one but I too was curious, and after researching I have come up with the following.

Firstly, you could try putting the resources into a flat Zip file (resource/json/templates.zip) nested in the Jar, then loading all the resources from that zip file since you know all the zip entries will be the resources you want. This should work even in the IDE.

String path = "resource/json/templates.zip";
ZipInputStream zis = new ZipInputStream(getClass().getResourceAsStream(path));
for (ZipEntry ze = zis.getNextEntry(); ze != null; ze = zis.getNextEntry()) {
    // 'zis' is the input stream and will yield an 'EOF' before the next entry
    templateJson = (JSONObject) parser.parse(zis);
}

Alternatively, you could get the running Jar, iterate through its entries, and collect the ones that are children of resource/json/templates/ then get the streams from those entries. NOTE: This will only work when running the Jar, add a check to run something else while running in the IDE.

public void runOrSomething() throws IOException, URISyntaxException {
    // ... other logic ...
    final String path = "resource/json/templates/";
    Predicate<JarEntry> pred = (j) -> !j.isDirectory() && j.getName().startsWith(path);

    try (JarFile jar = new Test().getThisJar()) {
        List<JarEntry> resources = getEntriesUnderPath(jar, pred);
        for (JarEntry entry : resources) {
            System.out.println(entry.getName());
            try (InputStream is = jar.getInputStream(entry)) {
                // JarEntry streams are closed when their JarFile is closed,
                // so you must use them before closing 'jar'
                templateJson = (JSONObject) parser.parse(is);
                // ... other logic ...
            }
        }
    }
}


// gets ALL the children, not just direct
// path should usually end in backslash
public static List<JarEntry> getEntriesUnderPath(JarFile jar, Predicate<JarEntry> pred)
{
    List<JarEntry> list = new LinkedList<>();
    Enumeration<JarEntry> entries = jar.entries();

    // has to iterate through all the Jar entries
    while (entries.hasMoreElements()) {
        JarEntry entry = entries.nextElement();
        if (pred.test(entry))
            list.add(entry);
    }
    return list;
}


public JarFile getThisJar() throws IOException, URISyntaxException {
    URL url = getClass().getProtectionDomain().getCodeSource().getLocation();
    return new JarFile(new File(url.toURI()));
}

I hope this helps.

xtratic
  • 4,600
  • 2
  • 14
  • 32