0

I'm working on a project where having access to a big resource folder (structure with thousand of little images) is required. The client want to offer the app through a native installation (which includes the JVM that the app require to run). He doesn't want to pack that resources as a folder in the app because it would create a folder structure as big as the original in the final user's hard drive (the folder doesn't take much space but it has many little files), plus the folder could be stealed easily by simply copying it. Giving this, I can't package all the app with a resource folder in a jar file, as far as i know jar files are not installables. Another requirement is that client needs certain flexibility to add some files in a installed app folders structure to add new features to the program. So an installation is the only way (i think) to obtain this.

I've tried to pack them in a jar file, include it in the build path and tried to access it but i failed even with all the research i've made through various sites. Tried getResources() in a million ways but it was impossible to get a simple directory inside the jar file meanwhile doing it from a folder outside the jar is really easy. I need to get access to a directory in order to get a list of files it cointains.

Arrived to this point. I've started to ask myself if i'm facing this problem on the best way so i wanted to ask you all: how would you package the resources you need in a native java app with this requirements?

I'm even thinking about create some kind of encryption proccess to create a single file with all the information and simply temporarily decrypt it when needed at runtime but i think there would be a simpler and cleaner way to face this.

Thank you in advance

EDIT: As you asked for, i'm adding the code of what i've tried:

this is the project structure

project
├───src
│   ├───main
│   │   └───java
│   │       ├───model <--- where my class is
│   │       ├───controllers
│   │       ├───utilities
│   │       └───views
│   ├───resources <--- this is where is (formerly) accessed the content i need
|   |   ├─── jarfile.jar <--- i've placed file here and included to build path
│   │   └───-various folders and files -
│   └───test
└───target

inside the jar file there are the packages src.resources.blalblaba and inside of this, the folder i need

Way1:

getResources replacing jar file "." with "/" tried with paths: "src/resources/blablabla/folderINeed","src/resources/src/resources/blablabla" (due to possible duplicity), "folderINeed", "blablabla/folderINeed" -> URI always get NullPointerException with message "null"

public void loadContent(String contentPath) throws Exception
{ 
    File resources= null;
    File[] listFiles = null;

    URI uri = getClass().getClassLoader().getResource(contentPath).toURI();
    resources= new File(uri);
    listFiles = resources.listFiles();

    //do some file proccessing and load them
}

Way 2: paths used "folderINeed","src/resources/blablabla/folderINeed","blablabla/folderINeed","../../../resources/blablabla/folderINeed" <--- URL return null but, at least, doesn't raise a NullPointerException.

public void loadContent(String contentPath) throws Exception
{ 
    // conseguimos todas las carpetas de animaciones
    File resources;
    File[] listFiles = null;

    URL url = MyClass.class.getResource(contentPath);
    if (url == null) {
         // error - missing folder
    } else {
        resources = new File(url.toURI());
        listFiles = resources.listFiles();
    }
}

Way 3: some complex code using class JarFile that didn't work for me and was oriented to get a simple file, not a folder. Obtained here

Eos
  • 1
  • 1
  • 5
  • 1
    Packaging images into the .jar file is the correct way to handle this. Please update a question and include at least one of the million ways you tried to use getResources(), along with the structure of the jar file, which didn't work for you. – yole Jul 26 '18 at 09:53
  • ok, i'll do that @yole. – Eos Jul 26 '18 at 10:43
  • Don't put a .jar file in your resources folder. Instead, package your entire app, with .class files and images together, into a .jar file. – yole Jul 26 '18 at 10:53
  • @Yole I've thought about that but the client want to present the app through an installer so it can be installed in a native way along the JVM that is needed to run it. Giving this i think it's not possible to do that. I'll edit the question adding this detalis – Eos Jul 26 '18 at 11:05
  • How is that not possible? IntelliJ IDEA on Windows and Mac is distributed exactly like that. You have a native installer that installs the JRE and a bunch of .jar files, some of which contain .class files and others contain images. (Could be a single .jar file, too - doesn't matter.) – yole Jul 26 '18 at 11:07
  • I'm not discussing that but i've installed IntelliJ IDEA and you can access its images through the folder structure (a simple search of *.png or another image extensions reveals them) and remember that we don't want to create a copy of the resource folder once de app is installed, this is why i've trying to pack them in a jar on first place. I don't know maybe i'm misunderstanding your point. – Eos Jul 26 '18 at 11:34
  • For some reason, the Android plugin includes some .png files outside of .jar files. The rest of IDEA's components package all images in .jar files. And there is no need to create a copy of anything; getResourceAsStream() allows you to load the icons from the same jar where your application code is located. – yole Jul 26 '18 at 12:46

2 Answers2

0

src/main/java/ is the build convention of maven, so I'll assume it is that.

Then one normally would pack the images as read-only resources in its own jar, a separate maven project jarfile.

jarfile/src/main/resources/ would be the root directory for the images. For instance: jarfile/src/main/resources/icons16/new.png would then be accessed by something like:

getClass().getResource("/icons16/new.png"); // Class' package relative path
getClass().getResourceAsStream("/icons16/new.png");
getClassLoader().getResource("icons16/new.png"); // Absolute class path path

In the original project one would add a dependency in the pom.xml to jarfile.

If you do no use maven, the jarfile.jar must be on the class path.

File cannot be used with resources, but getResourceAsStream most often is feasible.


To read a resource directory, the directory path should be rather unique, and it is simply a list of file names in the directory resource.

    InputStream in = App.class.getResourceAsStream("/icons16");
    try (BufferedReader rd = new BufferedReader(new InputStreamReader(in, "UTF-8"))) {
        String line;
        while ((line = rd.readLine()) != null) {
            System.out.println("line: " + line);
        }
    }
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • Yes, you're right, it's a maven project. I appreciate a lot the effort but, as i said in the question, i need to access a folder in order to list all it's content not file by file because 1) i won't know what resource folder will contain in a future. That way we can add more material to resource folder in other versions and 2) there are a lot of files to hardcode. giving this, i didn't succed accesing a folder using getResource or getResourceAsStream. – Eos Jul 26 '18 at 11:10
  • I have added the answer with reading a resource directory. I generally do not do such a thing, and do not know whether this is an official solution. One would need to look for a java reference. – Joop Eggen Jul 26 '18 at 11:34
  • The `getResource(...)` gives an URL like `jar:file://.... jarfile.jar!icons16/new.png` with which one could make a file system view. That would be both more uniquely identifiying the jar, and be more normal. However I have no more time today. – Joop Eggen Jul 26 '18 at 11:44
  • I've tried that without adding the maven dependency and replacing "/icons16" with "folderINeed" but still getting null in the input stream. I'm finding out how to add a local dependecy to maven and will retry then. – Eos Jul 26 '18 at 11:53
  • Fine, open the jar (zip format) with some zip tool and check the path; case sensitive and so on: slash /, Class with leading /, ClassLoader without leading /. – Joop Eggen Jul 26 '18 at 12:38
  • After a few days trying i think i get the problem. I've tried, in a toy project, to access a jar file using getResourceAsStream(). I've realized that if a use as string parameter "/" i get the package where my class is placed as output. After a few tries the farthest i could reach withouth getting an error was "../../". After that point, the code outputs "null". So Even if i add the jar to the build path getResourceAsStream() seems to be unable to reach it because it's scope ends at src/main/java. – Eos Jul 30 '18 at 11:54
  • So src/resources is unaccessible by that method. in other words, adding it to the build paths seems to be pointless (at least on this case). Once i realized that i was able, at least, to read as stream "src/main/java/package/resources/animaciones.jar" i started to think that, i don't know, maybe i would need to reorganize the folders and look a workaorund from there. :( – Eos Jul 30 '18 at 11:54
  • src/resources should have been src/main/resources by maven convention. There is src/main for the product, and src/test for the unittests. And under that the real root "source" directories: java, resources, webapp and such. Maybe the IDE settings are skewed. Looking in the jar with some zip tool should be informative. Or make a new playground project to test things. – Joop Eggen Jul 30 '18 at 12:04
  • Well, thanks to your last comment i managed myself to find a path within all this mess. I'm not using exactly the technich that you've suggested but your aportation about the maven convention for the resources folder was the key. I'll update my question with what i've done. I just want to thank you a lot for your guidance, pathience and effort. – Eos Aug 02 '18 at 09:05
0

MY WRONGS:

  • As @Joop Egen told in his answer, one of my problem was my folder structure in the project. I was not following the maven convention of putting the resource folder in the src/main/ folder it's because of this all the solutions i were trying didn't have the necessary scope to get the resource folder.

  • I didn't know how jar files work with java. For java .jar files are a collection (not a Collection of java) of jar entries to every single file within them. In my single case, the .jar file was created using the Eclipse export wizard and didn't have any reference to folders, it just had references to files. So it's simply IMPOSSIBLE to get a folder with all its content.

  • I used the java JarFile class to manage the content but this class doesn't offer methods to manage files like java File class does. So it's not as easy to do as it is with another kind of files.

WHAT I'VE DONE:
I've developed a code to read all the file entries in the .jar, discriminate the ones i was interested to. And then extract them to a directory within the app. By doing this i had standard access to them and, if i want to, i can simply remove the directory when the application closes. I was trying to use them directly from the jar but jar files are zip files so in some moment that inner files need to be extrated from the jar to somewhere as OS do with zip files. It's can a be a temp directory or not.

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.commons.io.FileUtils;

public class App
{
  public static void main(String[] args)
  {
    try
    {

      //needed attributes
      String pathToJar = "./src/main/blablalba/fileName.jar";
      String folderYouWantToRetrieveFromTheJar = "/folderIWant";
      String pathOfFilesWithinTheJar="src/resources/blablabla/"+folderYouWantToRetrieveFromTheJar+"/";
      String tempDirectoryWhereExtract="./src/main/resources/temp";

      //creating the temp directory
      File tempDirectoryReference = new File(tempDirectoryWhereExtract);
      if (!tempDirectoryReference.exists())
      {
        Files.createDirectory(tempDirectoryReference.toPath());
      }

      //searching what entries i need
      JarFile jar = new JarFile(pathToJar);
      final Enumeration<JarEntry> entries = jar.entries(); 
      List<JarEntry> targetEntries = new ArrayList<>();
      while (entries.hasMoreElements())
      {
        JarEntry entry = entries.nextElement();
        //if the entry is what i need 
        if (entry.getName().startsWith(pathOfFilesWithinTheJar))
        { 
          targetEntries.add(entry);
        }
      }
      //extract every target entry from the .jar
      for (JarEntry entry : targetEntries)
      {
        //in order to copy the structure i will get only the point where folderIWant is present
        int index = entry.getName().indexOf(folderYouWantToRetrieveFromTheJar);
        String newTemporaryPath = tempDirectoryReference.getPath().toString()+"/"+entry.getName().substring(index);
        extractFileFromJar(jar, entry, new File(newTemporaryPath));

      }

      jar.close();
      //(optional) clean after use
      FileUtils.deleteDirectory(tempDirectoryReference);


    }
    catch (IOException e)
    {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  public static void extractFileFromJar (JarFile jarFile, JarEntry targetEntry, File destinyPath)
  {
    try
    {
      if (!destinyPath.getParentFile().exists())
      {
        createFolderStructure(destinyPath);
      }
      else
      {
        Files.createFile(destinyPath.toPath());
      }

      InputStream inputStream = jarFile.getInputStream(targetEntry); 
      FileOutputStream outputStream = new java.io.FileOutputStream(destinyPath);
      while (inputStream.available() > 0) {  
          outputStream.write(inputStream.read());
      }
      outputStream.flush();
      outputStream.close();
      inputStream.close();
    }
    catch (IOException e)
    {
      e.printStackTrace();
    }
  }


  private static void createFolderStructure(File destinyPath)
  {
    File parentPath = destinyPath.getParentFile();
    try
    {
      if (parentPath.exists())
      {
          Files.createFile(destinyPath.toPath());
      }
      else
      {
        Files.createDirectories(destinyPath.getParentFile().toPath());
        Files.createFile(destinyPath.toPath());
      }
    }
    catch(IOException e)
    {
      System.err.println(e.getMessage());
    }
  }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Eos
  • 1
  • 1
  • 5