Media folder in jar file - java

This question has been brought up before, and I have searched many of the answers. It always ends in "You want getResourceAsStream". This is not what I am looking for.
My issue is , for a game object, I am using a folder structure to keep sprite strips rather than having one large sprite. This results in :
Media/
CharacterName/
AnimationName/
image.extension
the programming object just holds it's folder as a string, and I pass the getResource() URL to an object to fill the map of images. there can be {n} number of AnimationName/ sub directories. My error comes from this code:
dir = new File(s.toURI());
I take the directory, and call listFiles and pass the file names found to the sprite loader. Here is a code snippet:
dir = new File(s.toURI());
File[] chld = dir.listFiles();
//get a list of files in the image/character folder
for(File f:chld)
{
//get a list of the files for each dir
File[] grandChild = f.listFiles();
for(File t:grandChild)
{
String fname = t.getAbsolutePath();
System.out.println(fname);
String temp = fname;
temp = temp.substring(temp.lastIndexOf("/") + 1,temp.lastIndexOf("."));
String animName = temp.replaceAll("\\d*$", "");
int numPics = 0;
Pattern p = Pattern.compile("[0-9]+");
Matcher m = p.matcher(temp);
while(m.find()){
numPics = Integer.parseInt(m.group());
}
System.out.println("animation name: " + animName);
System.out.println("file name: " + fname);
System.out.println("number of pictures: " + numPics);
Animations.put(animName, sl.loadStripImageArray(fname, numPics));
}
}
Excuse the poor naming and temp variables, it's still being worked on.
sl is the sprite loader, and Animations is a hash map. This works fine until I package the project. I don't want to write a bunch of convoluted code that only works if I have a jar file, and not when I'm working in netbeans with the source folders.
I have considered having an application data folder, but I'd like to stay with a jar package if I can.

You do still want to use getResourceAsStream. Nothing here requires that all resources must be kept at the same folder within the JAR. You can use relative paths, or absolute paths to the root of the JAR by prefixing your path with /.
You can't make File work with resources within the JAR - even if instantiated with a URL that points to a resource contained within a JAR.
You may have to rework some other things, as the classpath is not really meant to be enumerated against (as you're currently listing files from the parent directory). It is designed to retrieve a resource by name. So one possibility (that I would recommend) is to have a "manifest" file that contains the files you want to load from each directory. (Read this file, then load the additional resources by name.)
Alternatively, if you can find the name of the JAR file you're loading from, you can create a Jarfile from it, then call its entries() method to find all of the contained resources. But even then, they aren't returned in a "tree structure", so ideally, you'd read this one, create your own tree structure from it (possibly as a series of Maps), then use it to retrieve the "directory listings" as needed.

If you are absolutely sure that the sprites are located in a jar - you could try using the JarFile class. There is a method entries. I didn't try it but it seems to return all resources located in the whole jar file. You would have to filter out which resources are in the right path.

Related

Java NullPointerException only on Linux

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.
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]);
}

Why can't java find my file?

I'm trying to see how many of these text files exist, but even with them there, the program always says the numFiles = 0. I have the files in a folder called Levels within the src folder. Thanks
int numFiles = 0;
for(int i = 0; i < 24; i++){
File file = new File("/Levels/level" + (i+1) + ".txt");
if(file.exists()){
numFiles++;
}
}
System.out.println(numFiles);
Edited
I overlooked that DirectoryStream doesn't support count()
You could go with an absolute path and make use of Stream API and lambdas. Like so:
String dirString = "..." //absolute Path
Path dir = Paths.get(dirString);
int numFiles = dir.getNameCount();
System.out.println(numFiles);
One advantage is that you can rename the files at will as long as they stay in the same directory. If you only want to work with specific files you can use filter() like so:
Files.newDirectoryStream(dir).filter(Predicate);
or add the filter directly when creating the DirectoryStream like so:
Files.newDirectoryStream(dir, RegEx);
To do something with each File you can use the consumer forEach() or have a look at Stream JavaDoc for other consumers/intermediate operations. Also double check if the DirectoryStream supports the Stream operation you want to use.
Your path is incorrect - if you are referring to an absolute location only then start with a /.
Also if you are using an editor remember your Java files are in src but but you don't run Java File you run class files and the class files may be in your bin/build directory most likely - check if the text file are in the build or bin directory.
Your path is incorrect, if you are referring to a local file(like something in your project folder) use
File file = new File("Levels/level" + (i+1) + ".txt");
the slash you used in front of the name makes it look in the root of the drive, not the local directory.

list of file names sort to a specific order

My java code lists all code files under a directory of file system, and load each file one by one:
File[] files = mDir.listFiles();
for(File f: files) {
System.out.println(f.getPath());
//load code file
System.load(f);
}
The above code logically looks good, but is not suitable for my case.
My case is that I can NOT load them in a loop one by one, because there are dependencies among those code files. I need to load the files in a specific order according to dependencies.
Say, I already know there are following files under the directory mDir which should be load in the following order:
["dFile", "xFile", "aFile", "hFile"]
and I already got the directory instance mDir .
How can I load files with above order efficiently in java?
If you already know which files you are interested in then just load them in the proper order.
If you have to see which files are available first and then load them in the specific order, then use one loop to get the names of the existing files, then process the list by picking the correct files in the correct order.
I'd suggest just setting the working directory correctly (see Changing the current working directory in Java?) and then doing
for(String fname : fileArray) {
System.load(new File(fname));
}
(where fileArray is the list of file names) or
for(String fname : fileArray) {
System.load(new File(mDir.getPath() + fname));
}
if you're intent on loading from a specific directory.
Other than that, you'd need to divine the dependencies from each file in order, or read the list of files to load from some other source (an array, another file, whatever).

get all absolute paths of files under a given folder

I need to hold in memory all absolute paths of file names under a given directory.
myDirectory.list() - retrieves String[] of file names only (without their absolute paths).
Don't want to use File Object since it consumes more memory.
Last thing - I can use apache collections etc. (but didn't find anything useful for that).
String directory = <your_directory>;
File[] files = new File(directory).listFiles();
for(File file : files){
if(file.isFile()){
System.out.println(file.getAbsolutePath());
}
}
This works, and I gotta say I'm confused when you say you don't wanna use File objects, but whatever works, I guess.
Doesn't myDirectory holds the directory of all those files?
If so, just combine the path in myDirectory with each of the cells in the array myDirectory.list() returns.

List assets in a subdirectory using AssetManager.list

My application has an assets directory in which I've dumped a bunch of text files I need to load at runtime.
I have a directory full of assets of a particular type (i.e., "assets/subdir") and I want to load all of the files in this directory, one at a time.
I have code like this:
AssetManager assetMgr = getAssets();
String[] assetsIWant = assetMgr.list("subdir");
for(String asset: assetsIWant) {
doAssetyThing(asset);
}
I've tried a zillion different versions of the parameter to assetMgr.list() and am not getting anywhere.
If I use "/", I get back a list containing the "assets" directory, and a few random other items (META_INF, for example). If I pass any other string (like "assets" or "assets/" or "/assets" or "/assets/" or "mysubdir" or "/mysubdir" or "assets/mysubdir" or ...) then I get back an empty array.
The documentation is unfortunately fairly incoherent.
Does anybody know what the correct formula for that list() parameter is?
Passing an empty string seems to work for me. I get a list of the files in my assets directory when I do the following:
AssetManager aMan = appContext.getAssets();
String[] filelist = aMan.list("");
I ve use the following code to list the file name in assets/myFolder/:
String[] fileNames =getAssets().list("myFolder");
for(String name:fileNames){
System.out.println(name);
}
note that the parameter in the method list does not contains "/".
When you need to have access to a folder down deeper in your hierarchy use
String[] fileNames =getAssets().list("myFolder"+File.separator+"mysubfolder");
instead of "/" inside the String, which would give you an empty String array as result.
I'm not sure why it works this way, but when I list "/", I get root level stuff, not things from my "assets" directory. I actually found no way to properly list my assets folder in the short time I spent trying.
The workaround I'm using is to create a "subdir" directory inside of my "assets" directory. I put all the assets that I want to access in this "subdir", and do:
String[] assetsIWant = assetMgr.list("subdir");
I don't know why "/" lists the "assets" directory itself as one of it's files, yet you don't have to specify the "assets" directory when giving list a path. Maybe someone else knows, but I hope this tip will help you out anyway.
There are a lot of similar questions around this topic, I suspect you are using the assets folder in a library project. if you try to access assets in a library project on android you will get empty contents and the exact symptoms you have described here
from the android "projects" documentation
source : http://developer.android.com/tools/projects/index.html
Library projects cannot include raw assets
The tools do not support the use of raw asset files (saved in the assets/ directory) in
a library project. Any asset resources used by an application must be
stored in the assets/ directory of the application project itself.
However, resource files saved in the res/ directory are supported.
if this is the case, the answer is that you should only include Raw assets in the assets folder of your application project NOT in any project marked as a library (you can check this by right clicking your project and selecting properties, select the android entry on the left and look at the is library checkbox)
Let's say you have this folder set up: Assets->SubDir1. You have things in Subdir1 like A.txt, B.txt, C.txt. So from the Project's directory, it would be Assets/SubDir1/A.txt.
Taking that into account, the way to access that file is similar to what you're doing, adding one thing. That one thing is in your doAssetyThing function. You have to put the SubDir1 directory in front of it, even if you did a filter of "SubDir1" in the .list("");
Example:
AssetManager am = getResources().getAssets();
String [] list = am.list("SubDir1");
for (String s:list) {
doAssetyThing("SubDir1/" + s);
//I use this next line as the start of copying a pdf from one location
//to another. I wanted to give you a real function in use.
InputStream inStream = am.open("SubDir1/" + s);
//more code
}
I hope this is clear and helps you out!
I use the following two methods to ensure given asset is present:
protected boolean hasAsset(String id) {
return hasAsset(id,"");
}
protected boolean hasAsset(String id, String dir) {
int idx = id.indexOf('/');
if (idx > 0 && idx < id.length() - 1) {
if (dir.length() > 0) {
dir = dir + "/" + id.substring(0,idx);
} else
dir = id.substring(0,idx);
return hasAsset(id.substring(idx + 1), dir);
}
try {
return Arrays.asList(context.getAssets().list(dir)).contains(id);
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
For some reason, empty directories are not listed.
At least, in my experience, they are not listed.
That is, if you have some-dir/d1/f1 and some-dir/d2 in the assets area, and you list("some-dir"), d2 is not listed.
This solution seems working for me:
Keep a copy of each file you want to access from a sub directory in the assets directory too.
Use same convention. i.e. list("subdirectory")
It doesn't read the files in assets folder, but reads all "copied" files in its sub directory.
Weird!! But works!

Categories