Not the jar that the game is in, but another jar. how would I go about it?
import java.awt.Image;
import java.awt.Toolkit;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Provides access to resources in zip files.
* <p>
* Usage: Resource resource = ZipResource.getDefault();
*
* @author Christoph Aschwanden
* @since September 14, 2010
* @access Public
*/
public final class ZipResource extends Resource {
/** The scheme. */
private static final String SCHEME = "zip:";
/** The instance of this class. */
private static ZipResource instance = new ZipResource();
/**
* The constructor.
*/
private ZipResource() {
// not used
}
/**
* Returns true if the scheme matches.
*
* @param The scheme. "zip:" for a match.
* @return True for matching.
*/
public static boolean matches(String scheme) {
return scheme.equalsIgnoreCase(SCHEME);
}
/**
* Returns an instance of this class.
*
* @return The instance of this class.
*/
public static ZipResource getDefault() {
return instance;
}
/**
* Returns the image for a certain path.
*
* @param path Path and name to the image. "zip:" is optional. Whatever is
* listed after the "!" is considered the image file to read.
* <p>
* Example:
* <ul>
* <li>zip:data/map/map_1.zip!picture1.png
* </ul>
* @return The requested image or null if it couldn't be found.
*/
public Image getImage(String path) {
// read in the bytes and create image from bytes
try {
String[] parts = adapt(path).split("!");
ZipFile zipFile = new ZipFile(parts[0]);
ZipEntry entry = zipFile.getEntry(parts[1]);
if (entry == null) {
// no entry
return null;
}
else {
long size = entry.getSize();
if (size < 0) {
return null;
}
else {
// read data into byte array and return the byte array
int bytesRead = 0;
int bytesToRead = (int)size;
InputStream inputStream = zipFile.getInputStream(entry);
byte[] data = new byte[bytesToRead];
while (true) {
int result = inputStream.read(data, bytesRead, bytesToRead - bytesRead);
if ((result == -1) || ((bytesToRead - bytesRead) == result)) {
// close the stream and return the data
inputStream.close();
inputStream = null;
return Toolkit.getDefaultToolkit().createImage(data);
}
else {
bytesRead += result;
}
}
}
}
}
catch (IOException e) {
return null;
}
}
/**
* Gets the input stream for a given path.
*
* @param path Path to use. "zip:" is optional. Whatever is
* listed after the "!" is considered the file to read.
* <p>
* Example:
* <ul>
* <li>zip:data/map/map_1.zip!music_piano.mid
* </ul>
* @return The input stream.
* @throws IOException If the input steam cannot be obtained.
*/
public InputStream getInputStream(String path) throws IOException {
String[] parts = adapt(path).split("!");
ZipFile zipFile = new ZipFile(new File(parts[0]));
ZipEntry entry = zipFile.getEntry(parts[1]);
if (entry == null) {
// no entry
return null;
}
else {
// return the stream
return zipFile.getInputStream(entry);
}
}
/**
* Returns a list of all files in the given path.
*
* @param path The path in where to look for files. "zip:" is optional. Whatever is
* listed after the "!" is considered the prefix.
* <p>
* Example:
* <ul>
* <li>zip:data/map/map_1.zip!pictures/
* </ul>
* @return A list of path strings. Or null if they couldn't be found.
*/
public String[] getFiles(String path) {
try {
String[] parts = adapt(path).split("!");
ZipFile zipFile = new ZipFile(new File(parts[0]));
String search = parts[1];
Enumeration<? extends ZipEntry> entries = zipFile.entries();
ArrayList<String> paths = new ArrayList<String>();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().startsWith(search)) {
if (!entry.isDirectory()) {
paths.add(entry.getName());
}
}
}
return paths.toArray(new String[0]);
}
catch (Exception e) {
return null;
}
}
/**
* Returns a list of all directories in the given path.
*
* @param path The path in where to look for sub directories. "zip:" is optional. Whatever is
* listed after the "!" is considered the prefix.
* <p>
* Example:
* <ul>
* <li>zip:data/map/map_1.zip!pictures/
* </ul>
* @return A list of path strings. Or null if they couldn't be found.
*/
public String[] getDirectories(String path) {
try {
String[] parts = adapt(path).split("!");
ZipFile zipFile = new ZipFile(new File(parts[0]));
String search = parts[1];
Enumeration<? extends ZipEntry> entries = zipFile.entries();
ArrayList<String> paths = new ArrayList<String>();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().startsWith(search)) {
if (entry.isDirectory()) {
paths.add(entry.getName());
}
}
}
return paths.toArray(new String[0]);
}
catch (Exception e) {
return null;
}
}
/**
* Returns the path so it can be utilized here.
*
* @param path The path. "jar:" is optional.
* @return The usable path.
*/
private String adapt(String path) {
if (path.indexOf(SCHEME) >= 0) {
// cut scheme
return path.substring(SCHEME.length());
}
else {
return path;
}
}
}
Look at Slick’s resource loader class. It looks first on the file system, then in the classpath. All JARs for your app make up the “classpath”.
You can load the bytes for your image many ways: from a file, from a socket (eg, a URL to a webserver), from the classpath, etc. To load from the classpath you can use one of:
SomeClass.class.getResourceAsStream(path);
getClass().getResourceAsStream(path);
The path you pass is a string. Starting the string with “/” will start the search for your file at the root of the classpath. Eg:
InputStream input = getClass().getResourceAsStream("/stuff/image.png");
Note there are other ways to look up classpath resources relatively to a class’ package directory, but I find just always using the above is easier.
Thank you guys very much. I managed to get what I wanted from this simple source:
try{
ZipFile zip = new ZipFile("data/graphics/player.zip");
ZipEntry entry = zip.getEntry("player.png");
player = new Image(zip.getInputStream(entry), "player.png", false);
}
catch(Exception e){
}
What’s the reason for this? I actually do it the other way around because I assumed Java would be faster at looking at stuff in the classpath. But come to think of it if it has to unzip the JAR each time (although it would be crazy if it did) then obviously navigating around in the classpath is much slower. And also I would rather not load something from the file system first, just in case the user puts some crazy junk in there. But for mods and the like I suppose this has an advantage.
Are the comparisons negligible, or is there a discernible difference?
CyanPrime, chances are good your code is wrong, since you catch Exception and do nothing. If you think it will never fail, good code uses:
} catch (Exception ex) {
throw new RuntimeException("Error loading playing image.", ex);
}
That does two things: 1) Adds a nice message so you know what the failure is. B) Throws an exception so you will get a stacktrace. You shouldn’t get in the habit of writing empty catches, even during the prototype stage, because if you ever leave just one behind, it is a big problem. During prototyping I like to make my main and other methods throw Exception. Then I can come back later, remove the “throws Exception” on main, and the compiler shows me all the places I need to properly deal with exceptions. If you really think it will never happen then you can use:
} catch (Exception ex) {
throw new RuntimeException(ex);
}
A lot of people use this:
} catch (Exception ex) {
ex.printStackTrace();
}
This is simply not as good for a “should never happen” exception, because it allows execution to continue, and all bets are off. While I’m here, this is also extremely wrong:
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
This is just as bad as doing nothing since it both hides the stacktrace and allows execution to continue. Any code that calls getMessage on an exception is probably hiding the stacktrace and bad. Finally, if you really, actually don’t care whether the exception is thrown, I like to use this:
} catch (Exception ignored) {
}
This way there is no confusion that the intent is to ignore the exception, and no one (intelligent) is ever going to come back later and call methods or rethrow a variable named “ignored”.
Solely convenience. The file system should always be faster though. It is simpler, only one place needs to be checked, and there is no compression.
There’s a little known feature for JARs where you can include an index of the files in the JAR to speed up looking for classpath entries.
There’s an exploit where Windows looks in the current directory for a library before the program directory. So you can put an MP3 and a DLL in a directory, and when a user runs the MP3, Windows Media Player (or whatever) runs with that as the current directory, the DLL happens to be named the same as one WMP wants to load, so Windows uses your DLL instead, and now you own the machine.
That’s odd. ZIP contains this index at the end of the file. You can both stream in entries or move to the end, read the index and seek the entries. What would a JAR entry index add to this? Could you provide a link to docs of this feature?
Edit:
You seem to be talking about META-INF/INDEX.LST, which design is rather awful. It must also contain all the packages/classes of the (other!) JARs in the classpath. If you leave them out, those resources won’t be found. Ugh.
Yep, the INDEX.LIST file:
http://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#JARIndex
The “i” option for the jar tool creates it.