I'm writing a Java program which given a qualified-name (like: java.lang.String), fetches and returns the corresponding entry from the src.zip file in the JDK for further processing.
So far my program works fine for any qualified-name which refers to a specific .java source file; but I'm having trouble when the qualified-name refers to a whole package (like: java.util.*). In this case I want my program to return a listing of all entries in the given package.
The problem is it seems there's no way to (efficiently) do such a thing using the utilities provided in the java.util.zip.* package! I have tried both ZipFile and ZipInputStream and none recognize the directories in the src.zip file! They only return entries for individual .java source files!
In code language, both:
ZipEntry entry;
ZipInputStream zip = new ZipInputStream(new FileInputStream("src.zip"));
while((entry = zip.getNextEntry()) != null)
System.out.println(entry.isDirectory());
and:
Enumeration<? extends ZipEntry> zip = new ZipFile("src.zip").entries();
while (zip.hasMoreElements()) {
ZipEntry entry = zip.nextElement();
System.out.println(entry.isDirectory());
}
always return false; no directories at all!
Even the following code is useless and just returns null (which means Not Found):
ZipFile zipfile = new ZipFile("src.zip");
zipfile.getEntry("java/util/");
A work-around is to use either of the two iterations I listed above and perform an exhaustive search for the desired entries:
if (entry.getName().startsWith("java/util/"))
System.out.println(entry);
But clearly this is not efficient! Is there no way to either retrieve the entry of a directory in the src.zip file or to efficiently list the entries for a given directory path? Note that I want to directly process the ZIP file without extraction (for obvious reasons).
Update
As it's apparent from the discussion under Timothy Truckle's answer, the above results were achieved using the src.zip file from the latest Oracle JDK at the time of this writing (i.e. JDK-8 update-111). The results differ when using another src.zip file from a different JDK version (e.g. JDK-7 update-80). The credit goes to marabu for pointing out the unzip -l utility in the comments.
Note
While the problem of retrieving directory entries is solved, the problem of efficiently retrieving the list of entries contained inside a given directory path of a ZIP file is not solved. Yet the case is still closed, since according to Timothy Truckle's answer, this cannot be done in any other way than an exhaustive search in the entries, due to limitations of the ZIP format.
#RC. So your saying that it's related to this specific src.zip file? Because I've seen other ZIP files which do have entries for directories. – Seyed Mohammad
No and Yes. The ZIP file format does only know of files but not directories.
What you may have seen in other zip files is that they included a zero length file named . for each (sub-) folder. But it is not required nor default to do so.
But even if this special entries exist you could not handle them as folders within the ZIP directly (even that all files in the same subfolder are listed consecutively is by accident and neither required nor implied).
Related
This question already has answers here:
How to list the files inside a JAR file?
(17 answers)
Closed 1 year ago.
This method does not apply:
Files.walk(Paths.get("folder name"))
Because when the app is running, packaged as a jar, it does not contain File objects.
Is there a method for an app to walk through all the contents of one of its packages while it runs?
There is; open the jar file (using java.io.JarFile) and walk through the entries returned by entries(). There's also JarInputStream if your jar is being streamed in from elsewhere.
But, it sounds like you have equated 'jar file' with 'my application, at runtime'.
That's a tricky move. For starters, figuring out your own jar is possible but a little hacky, and more importantly, it then means your app straight up fails to work unless it is in jar form. This complicates deployment, development, and debugging.
There is no need to do it this way.
You can ask java for resources from 'whereever it is currently picking up class files'. A class file is a resource crucial to the running of your application. So is e.g. an icon file for a GUI app. Both should be coming from the same place, and where that is? That's abstracted away, and you should follow along with the abstraction, so that the 'load resource' code works just as well in debugging as it does at runtime.
The system for this is MyClass.class.getResource("file.txt") which will look for file.txt in the same place MyClass.class is located, even if it is in jar files, generated live, or obtained from a BLOB object in a database someplace, or streamed over a network.
The downside is, this system does not have an abstraction for listing files. Only for getting resources with a specific name.
The SPI system is the solution: Make a file that lists (one resource per line) the resources - instead of 'list dir', you 'parse each line of CONTENTS.txt' for the equivalent operation. You can then use annotation processors, if you must, to automatically create and maintain this content file.
ServiceLoader is baked into the JDK itself, but it's designed to load, specifically, class files and not other resources. But the principle is trivial and can be handwritten in about 5 lines of code.
You can iterate the contents of any ZIP / JAR file using Files / NIO, but you need to access the ZIP filesystem. Then you can call Files.find or Files.walk on the contents of the ZIP / JAR archive.
For example this will print the contents of every Path in a jar:
Path zip = Path.of("your.jar");
BiPredicate<Path, BasicFileAttributes> predicate = (p,a) -> true;
try (FileSystem fs = FileSystems.newFileSystem(zip)) {
for (Path root : fs.getRootDirectories()) {
try(Stream<Path> stream = Files.find(root, Integer.MAX_VALUE, predicate)) {
stream.forEach(System.out::println);
}
}
}
Once you have a Path object from the ZIP FileSystem, you can access the contents of the entries using NIO Files calls in the normal manner such as with Files.newInputStream or Files.copy.
I am a newbie. A wanna to check for existing a folder or file in directory in past.
For better. Example, i may a directory C:\Users\Admin\AppData\ and i wanna to check of existing a directory Test in that path. That maybe be checket by:
File file = new File(System.getenv("APPDATA") + "\\Test\\");
if(file.isDirectory()){
///...
} else { ////....}
But i wanna to check if that directory is deleted - when. Please help with code examples... be VERY and VERY thanks
Instead of the File class, I recommend looking at the Files class - it is there to help you do many things. For example, Files.createFile(...) will check to see if a file exists before creating. You can then pass a positive result to FileWriter(...) for your work.
You can check for the presence of a file of folder, but not that it was deleted (e.g. checking a log file of past actions). I recommend using the logic of "if not there then it never existed or was deleted". Another option when working with files is to use parameters to always overwrite the file if that is what you want.
You are asking a question about the operating system. What happens after a file or folder is deleted is unique to each operating system. A notional recycle bin's awareness of a file or folder's original location was, and where that content may have been moved to is specific to an operating system (and usually isn't just moved into another folder).
We are programming a game, which shall be startable from a .jar file. First we created a Project in IntelliJ and loaded the Images from a ZIP with the following code:
ZipFile zf = null;
try {
zf = new ZipFile(zipPath);
Image Image = ImageIO.read(zf.getInputStream(zf.getEntry("Block/Air.png")));
} catch (IOException ignored) {}
Now the attempt without the ZIP (just from the .jar) is:
Image image=ImageIO.read(getClass().getResourceAsStream(path+ "Block/Air.png"));
It doesn't load any texture. Do you have a better way to do this in combination?
Edit:Seems not to be the Problem.
Since jars are zip files you could place them in the jar file and placing them the classpath.
Image image=ImageIO.read(getClass().getResourceAsStream(path+ "Block/Air.png");
Path must be a relative path from a source path root. E.g. I have a file in "src/main/resource/my/cool/game/" path is "/my/cool/game".
If you want to use a zip file, it must be outside of your jar file. To load the zip, you could use a relative file path, which is the same if you start your game from Intellij and from dekstop.
To change the working directory in intellij look here.
The best way would be to place the zip file alongside the jar so you can use "." as working directory to load the zip file.
Alternatively you could use a fixed directory, but then your game needs some sort of installation so it knows where to finde the zip file.
If you use ".", the zip file needs to be in the root of the project directory.
The Class.getResource and Class.getResourceAsStream methods take a URL (not a file path!) which is relative to the root of each classpath entry. For classpath entries which are .jar files, this means the path of a file packaged within the respective .jar file.
If your entire program is in one .jar file, the classpath consists of just one item: that .jar file. Therefore, there is only one classpath root, and the String you pass to getResourceAsStream is the URL of an entry within your .jar file. Do not include the path to the .jar file in that String.
If you are not sure what you should pass, examine your .jar file's contents. Every IDE (that I know of) provides a way to do this. You can also use any unzip utility to examine a .jar file, since every .jar file is actually a .zip file. (If you only have Windows, with no zip tools installed, make a copy of the .jar file and change the copy's extension to ".zip", then open it.)
Inside the .jar file are, of course, zip entries. The full path of the entry you want to load (without the path to the .jar file) is what you must pass to getResourceAsStream. getResourceAsStream accepts a URL, and URLs always use forward slashes (/) on all platforms, so do not use any backslashes. Also, the first character of the String must be /.
It is actually possible to specify a shorter path, depending on how your images are packaged in the .jar, but that is a separate topic. See the documentation for full details.
Side note: Never, ever write an empty catch block. Ever. That caught exception is by far the easiest way for you or anyone else to know when and why your program is not working. At the very least, put exc.printStackTrace(); in your catch block. More often, the correct course of action is to abort the program with something like throw new RuntimeException(exc);. After all, your program can't continue to function properly if it can't load that image, right?
Why do you need to store your images in a zip file? If you're doing it to reduce the file size, you gain absolutely nothing from zipping it first. JAR files are zipped files anyway (if you don't believe me, rename your .jar file to .zip, and try to open it). What you're basically doing is attempting to zip an already zipped file, which doesn't really do anything.
I would recommend you unzip your images and store them somewhere like < resources >/images
If you insist on leaving them zipped, you'll need to change it to something like this. Otherwise, it's looking for the zip file in the working directory (directory from which the jar was executed)
ZipFile zf = new ZipFile(getClass().getResourcesAsStream("path/to/zip"));
Disclaimer: I am not familiar with the ZipFile class, so I do not know if that constructor exists.
I am having problem to use JWNL wordnet in a Jar file.
JWNL uses RandomAccessFile to read wordnet dictionary files. In order to create a Jar file, wordnet dictionary files are put in resources/wordnet folder. As resources is in my Build Path, I have no problem to run the application I created in Eclipse. However, when I use another application to run the created jar file, I get the following error:
java.io.FileNotFoundException: resources/wordnet/data.noun (No such file or directory)
from the following code:
RandomAccess _file = new RandomAccessFile(path, _permissions);
I use the following code to check the current working directory:
URL location = PrincetonRandomAccessDictionaryFile.class.getProtectionDomain().getCodeSource().getLocation();
System.out.println(location.getFile());
It seems both situation have the same location: /project/bin/
How should I fix the problem? Thank you
The key information you seem to be missing is that Jar files are compressed, and you can't "seek" because of the compression (which is I believe the DEFLATE algorithm).
However, you could extract the file(s) into temp file(s) on start and then use that. Temp files would be removed on application exit, and are the best answer I can think of.
RandomAccessFile to read files in a Jar file
There are no files in a JAR file. There are JAR entries. You can't read them with FileInputStreams, RandomAccessFiles, or FileReaders.You need to use a JarInputStream or its friends.
I'm writing a Java Class which extends Ant Zip Task to do a particular job for me. I want to create a zip file and once that file is created, I want to suppress the access time in the inode so I can't be modified or find a way to not let it change, even if the file is modified. The reason for that is I made a md5 hash which depends on the access time. Thus that's giving me a lot of trouble, and making the access time constant will solve my problem.
Does someone now how would I accomplish that?
Thanks!
I've had to solve a similar problem previously - perhaps this is an option for you. In my case, the problem was:
We made a jar file and then ran an secure hash algorithm on the jar file. Because the jar file is really a zip file, and a zip file internally contains file metadata information including last access time, if we create a new jar file from the exact same source material, then the hash on the new jar file doesn't match the original hash (because while the zip contents are the same, the metadata stored in the zip file has different file creation / access times).
Basically, we needed to be able to compute a secure hash for compliance purposes to be able to easily show that the contents of a jar was unchanged. Recompiling an equivalent jar was ok - it's just that the contents had to be identical.
We wrote a simple set of tools that performed secure hashes (and verifications) specifically for zip/jar files. It computed two hashes:
a regular secure hash of the file (which would identify the exact same jar - this would be the same as the output of your standard md5sum)
a "content only" hash which was computed by iterating over the bytes of the unpacked contents of the zip/jar (and thus could be used to identify that a recompiled jar matched the original jar)
To implement the content only hash, we used a ZipInputStream to iterate over the zip entries.
MessageDigest sha1;
byte[] digest;
for (each zip file entry)
{
if (entry represents a directory)
{
sha1.update( directory name bytes as UTF-8 );
}
else
{
read the entry bytes using ZipInputStream.read()
sha1.update( bytes );
}
}
digest = sha1.digest();
See also: ZipInputStream.read()
Note, however, that some files such as the manifest can contain information such as the version of ant used to create the jar, and the version of the compiler used to compile the classes. Thus, you have to compile from an equivalent environment for the hash to match.
Finally, this doesn't cope with the fact that a zip file might itself contain other zip files. While it would be straight forward enough to make the inspection cater for this and descend into nested zip/jar/war files, our implementation does not.