Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't access resource in a JAR on all computers

I'm writing an application (specifically a plugin for the Bukkit Minecraft server). Doing this requires that I access a .properties file from the JAR of the application. This is where I encounter a strange problem. When I test the program on my development PC, it runs just fine. The .properties file gets loaded and everything is fine. However, on the other computer that I test it on, I try to start the app, and it can't loaded the properties, and the InputStream is null. Here is the method in which I load the file:

public class Points {
    private HashMap<String, MessageFormat> messages;

    public Points() {
         buildMessages();
    }

public static void buildMessages() {
        Properties messageProps = new Properties();
        InputStream in = Points.class.getResourceAsStream("resources/messages.properties");
        messages = new HashMap<String, MessageFormat>();
        Enumeration en;
        try {
            messageProps.load(in);
        } catch(IOException ex) {
            System.err.println("Couldn't read message properties file!");
            return;
        } catch(NullPointerException ex) {
            System.err.println("Couldn't read message properties file!");
            if(in == null)
                System.out.println("IOStream null");
            return;
        }
        en = messageProps.propertyNames();
        while(en.hasMoreElements()) {
            String key = (String)en.nextElement();
            String prop = messageProps.getProperty(key);
            MessageFormat form = new MessageFormat(prop.replaceAll("&", 
                "\u00a7").replaceAll("`", ""));
            messages.put(key, form);
        }
    }
}

I've omitted some irrelevant code, but that is the gist of it. The structure of the JAR is as follows:

   com/
       pvminecraft/
           points/
               Points.java <-- The class where the file is loaded
               resources/
                   messages.properties <-- The file being loaded

On my PC the file is loaded from resources/messages.properties, but on the other file, the InputStream is null, and my catch block for the NullPointerException is run. What could be causing the problem, and how could I fix it? Thanks.

Update: Even using the full path (/com/pvminecraft/points/resources/messages.properties), the same issue is still persistent.

Update 2: Here is the full stack-trace:

java.lang.NullPointerException
    at java.util.Properties$LineReader.readLine(Properties.java:435)
    at java.util.Properties.load0(Properties.java:354)
    at java.util.Properties.load(Properties.java:342)
    at com.pvminecraft.points.Points.buildMessages(Unknown Source)
    at com.pvminecraft.points.Points.onEnable(Unknown Source)
    at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:188)
    at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:968)
    at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:280)
    at org.bukkit.craftbukkit.CraftServer.loadPlugin(CraftServer.java:186)
    at org.bukkit.craftbukkit.CraftServer.enablePlugins(CraftServer.java:169)
    at org.bukkit.craftbukkit.CraftServer.reload(CraftServer.java:436)
    at org.bukkit.Bukkit.reload(Bukkit.java:187)
    at org.bukkit.command.defaults.ReloadCommand.execute(ReloadCommand.java:22)
    at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:165)
    at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:378)
    at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:374)
    at net.minecraft.server.MinecraftServer.b(MinecraftServer.java:564)
    at net.minecraft.server.MinecraftServer.w(MinecraftServer.java:541)
    at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:425)
    at net.minecraft.server.ThreadServerApplication.run(SourceFile:457)

All of the org.bukkit and org.craftbukkit stuff is the server. The .properties file is loaded in the buildMessages method, called by the onEnable method of Points.

Update 3: On a fresh install of Arch Linux, the message properties file is loaded correctly and all is well. The remote server is Ubuntu Linux, and my dev PC is Arch.

Update 4: Alright, this is sort of a resolution. It seems to be a localized problem. I say that because I've managed to get access to two more computers, and the program runs correctly on both. While it's annoying, this doesn't seem to be anything wrong with my code or build scripts. I'm still wanting to know what's wrong, but it isn't pressing any more. I'll continue looking into this. Thanks everyone.

like image 569
Michael Smith Avatar asked Dec 23 '11 23:12

Michael Smith


People also ask

How do I give access to a jar file?

Open File Explorer and the folder that includes your JarFile. Right-click the Jar file and select Open with then Choose another app. Select Java if it's listed among the default programs.

How do I view the resources folder in a jar file?

No, resources work perfectly well in a JAR. Just don't treat a resource as a File, because it isn't one. Instead, use the getResourceAsStream() method and you'll have an InputStream from which you can read the contents of the resource.


2 Answers

Seems like minor subtleties between the different Java class loaders and their search paths. Before going into these details; why don't you try the full path within this jar file? (i.e. something like this:

Points.class.getResourceAsStream("com/pvminecraft/points/resources/messages.properties");

)

like image 164
Tom Avatar answered Sep 30 '22 06:09

Tom


Point.class.getClassLoader().getResourceAsStream("com/pvminecraft/points/resources/messages.properties");

Try it without the first '/' and it should work anywhere running a JVM.

If that didn't work, please try to put the file in the ROOT of the JAR file and try it again.

If still doesn't work, try using this method:

public static byte[] getFile(File zip, String fileName) throws FileNotFoundException, ZipException, IOException {
        String filename = fileName;

        if (!zip.exists()) {
            throw new FileNotFoundException(zip.getName());
        }
        while (filename.charAt(0) == '/' || filename.charAt(0) == '\\') {
            filename = filename.substring(1);
        }

        if (filename.contains("\\")) {
            filename = filename.replace("\\", "/");
        }

        ZipFile zipFile = new ZipFile(zip);
        Enumeration entries = zipFile.entries();

        ByteArrayOutputStream output;
        byte[] result = null;

        while (entries.hasMoreElements()) {
            ZipEntry entry = (ZipEntry) entries.nextElement();

            if (entry.getName().equalsIgnoreCase(filename)) {
                FileUtils.copyInputStream(zipFile.getInputStream(entry), output = new ByteArrayOutputStream());
                result = output.toByteArray();
                zipFile.close();
                output.close();
                return result;
            }
        }

        zipFile.close();
        throw new FileNotFoundException(filename);
    }

You will need this

public static void copyInputStream(InputStream in, OutputStream out) throws IOException {
    byte[] buffer = new byte[1024];
    int len;
    while (((len = in.read(buffer)) >= 0)) {
        out.write(buffer, 0, len);
    }
    out.flush();
}

Get the path of the running jar

 String currentJar = "";
                                        // Get current jar path. Since user may rename this file, we need to do this way
              try {
                   currentJar = (Points.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
                   if (currentJar.startsWith("/")) currentJar = currentJar.substring(1);
                   } catch (URISyntaxException ex) {
                   }

The first '/' I don't really remember why it appears, but it do, so you must remove it:

Finally call the method: getFile(currentJar, "PATH_TO_PROPERTIES_FILE");

You will have an array of bytes to work with. Just put it as a ByteArrayInputStream and your problems should be solved.


That code is part of a util class I've created, that's why the unecessary read to a byte array, but ofc, you can change it to use directly that InputStream to the Properties.load() method.

Link for the ZIP util class

http://all-inhonmodman.svn.sourceforge.net/viewvc/all-inhonmodman/ModManager/src/modmanager/utility/ZIP.java?revision=292&content-type=text%2Fplain

Link for the FileUtils util class

http://all-inhonmodman.svn.sourceforge.net/viewvc/all-inhonmodman/ModManager/src/modmanager/utility/FileUtils.java?revision=294&content-type=text%2Fplain

like image 22
SHiRKiT Avatar answered Sep 30 '22 06:09

SHiRKiT