6

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.

Michael Smith
  • 1,847
  • 2
  • 19
  • 19
  • The directory "resources" isn't on the classpath - see Tom's answer. The directory "com/pvminecraft/points/resources" is, however. Check your development environment - I believe you've got either the classpath set funny or a copy of the properties files sitting somewhere that is on the classpath. – Paul Dec 24 '11 at 02:26
  • Please post the entire stack trace. – Paul Dec 24 '11 at 17:52
  • what is the line number here: `at com.pvminecraft.points.Points.buildMessages(Unknown Source)`. what is your JDK version? – Kowser Jan 02 '12 at 15:56
  • @Kowser I use JDK 6u26 because of backwards compatibility problems I have with JDK 7. – Michael Smith Jan 02 '12 at 21:36
  • Can u put your properties in Manifest file? are you ok with reading properties from Manifest file? – Rajesh Pantula Jan 06 '12 at 11:12

4 Answers4

2

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");

)

eboix
  • 5,113
  • 1
  • 27
  • 38
Tom
  • 143
  • 4
  • It's the same story with that change. Works on my PC, but not the other. – Michael Smith Dec 24 '11 at 16:49
  • @MichaelSmith - how are you running it on your computer? – Paul Dec 24 '11 at 17:33
  • @Paul As I mentioned above, they are plugins for a server application. The server starts up and loads all of the plugins in the `plugins` directory. I'm running the same version of the server on both PCs. The second is actually a hosted machine that I don't have free access to. In terms of the server, the only difference between the two is that they are on different machines. I guess the host could have configured Java differently? – Michael Smith Dec 24 '11 at 17:36
  • @MichaelSmith, move the properties file to the root of your classpath, i.e., in the same directory as "com", and reference it as `/messages.properties`. This is not a Java configuration issue, though it is possible the remote server also has a file `messages.properties` which you are not allowed to load, so you could also try renaming `messages.properties` to `smith-messages.properties`...something unique. – Paul Dec 24 '11 at 17:42
  • @Paul I renamed it to `points-messages.properties` and moved it to the root of the jar. The same issue still persists. – Michael Smith Dec 24 '11 at 17:46
  • @MichaelSmith, did you try it with and without the leading front slash? – Paul Dec 24 '11 at 17:49
  • @Paul Yes. Without the leading slash it fails on both. With it, it works on my dev PC, but not on the server. – Michael Smith Dec 24 '11 at 21:45
2

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

SHiRKiT
  • 1,024
  • 3
  • 11
  • 30
0

You may also want to make sure if your build script (Ant, Maven) or your IDE didn't remove/relocate that messages.properties (coz it is not .class) from the resulting JAR file. You can check your JAR contents by using tools like 7zip or WinZip.

jocki
  • 1,728
  • 16
  • 26
0

Since you're loading a resource that's in the same package of your class (Point), you don't need to use the absolute path for loading it.

Does the server use any kind of cache for loading the plugins? This could be caused by an older version of the plugin jar being present in the class path. To verify that the server is really loading the correct version of the jar file, you could try deploying a version of your plugin which logs something to the console and see if something happens (if the message does get logged).

Also, I don't know how the class loader hierarchy is organized in the server, but you could try loading the resource from the current thread class loader (usually the root parent class loader, which will look for the resource in every other child class loader). You'd have to use the absolute path for that.

ClassLoader rootCL = Thread.currentThread().getContextClassLoader();
InputStream resource = rootCL.getResourceAsStream(
        "/com/pvminecraft/points/resources/messages.properties");

Check this question to learn more about the different class loaders.

Community
  • 1
  • 1
Andre Rodrigues
  • 2,214
  • 16
  • 14