getClass().getResourceAsStream() returns null after reload

Discussion in 'Plugin Development' started by Delocaz, May 5, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    Delocaz

    I'm working on a quite large general commands plugin (like Essentials or AdminCmd), but there is one hurdle I can't really jump over.

    Both my item manager and language file manager uses getClass().getResourceAsStream() to load their respective files from the .jar. When the server first starts, everything is fine, but after a /reload, suddenly NullPointerExceptions everywhere. The problem is that the getClass().getResourceAsStream() returns "null" after a reload, no matter what path it is given.

    The code in question is this:
    Code:
    BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/res/items.csv")));
     
  2. Offline

    Delocaz

    Nobody?
     
  3. Offline

    Lolmewn

    You could use .getResource from your JavaPlugin class.
     
  4. Offline

    Delocaz

    That returns null too...
     
  5. Offline

    Lolmewn

    Show us more code?
     
  6. Offline

    Delocaz

  7. Offline

    SpaceManiac

    I've noticed this too while developing a plugin; seems to happen only if the resource in question actually changes. I don't think this used to happen and I really have no idea what's going on. I noticed that getResource works some of the time referencing the ClassLoader directly doesn't work but I couldn't figure out why. Here's JavaPlugin.getResource for reference:

    Code:
        public InputStream getResource(String filename) {
            if (filename == null) {
                throw new IllegalArgumentException("Filename cannot be null");
            }
    
            try {
                URL url = getClassLoader().getResource(filename);
    
                if (url == null) {
                    return null;
                }
    
                URLConnection connection = url.openConnection();
                connection.setUseCaches(false);
                return connection.getInputStream();
            } catch (IOException ex) {
                return null;
            }
        }
    
    I've confirmed that getClassLoader() of the plugin is the same as getClass().getClassLoader() from where I'm using it and included setUseCaches also; really no idea what's up.
     
  8. Offline

    Delocaz

    Thing is, the resource never actually changes, it's just a static file residing in the .jar.
     
  9. Offline

    catageek

    I don't know if this is relevant, but I use this to load a FileSystemProvider in a Bukkit plugin. This implements the addURL method by reflection, maybe you can implement getResource as well.

    I think the problem is that Bukkit provides its own classloader that does not load plugin resources in all cases.

    Maybe this can help you. Put this class in your plugin:

    Code:
    import java.io.File;
    import java.io.IOException;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
     
    @SuppressWarnings({ "rawtypes", "unchecked" })
    final class LoadLib {
     
        private static final Class[] parameters = new Class[] { URL.class };
     
        public static void addFile(String s) throws IOException {
            File f = new File(s);
            addFile(f);
        }
     
        public static void addFile(File f) throws IOException {
            addURL(f.toURI().toURL());
        }
     
        public static void addURL(URL u) throws IOException {
     
            URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            Class sysclass = URLClassLoader.class;
     
            try {
                Method method = sysclass.getDeclaredMethod("addURL", parameters);
                method.setAccessible(true);
                method.invoke(sysloader, new Object[] { u });
            } catch (Throwable t) {
                t.printStackTrace();
                throw new IOException("Unable to add URL to classloader");
            }
     
        }
    }
    
    And call this when enabling the plugin:

    Code:
    LoadLib.addFile("plugins/myplugin.jar");
    
    Your method should return an InputStream instead
     
  10. Offline

    totemo

    I came here looking for a solution to the same problem. In my case, it manifests when I call ResourceBundle.getBundle(). The issue is apparently a long standing one, since I found this post about it from Aug 2011, with no resolution in sight:

    http://forums.bukkit.org/threads/resourcebundle-and-internationalisation.29099/

    I might add that I can reload my plugin as much as I like as long as I don't recompile it. I think Bukkit is detecting a change to the JAR and clearing its cache over-zealously.
     
  11. Offline

    garbagemule

    Gonna go ahead and "necro" the thread (I'd call it a shameless bump).

    I've had this issue for a long time. It used to pop up every single time I re-built my plugin and /reload'ed my test environment. I load auxiliary config-file templates from the jar, and I used to load them using the getResourceAsStream() method on the class loader. I've recently switched to a file-based approach (getting the contents of the file on disk, rather than through the class loader). This works regardless of how many times I replace the jar-file or reload the server, but it has the obvious disadvantage of having a dependency on the name of the jar-file, which can no longer be arbitrarily renamed (e.g. putting a version number at the end of the filename).

    In the meantime, I don't think this whole thing is a Bukkit-specific issue, but perhaps a bug in the underlying class loader(s) in the JVM. I found some StackOverflow entries a while back that talked about the same kind of issue with web-applications, but I didn't save the links.

    I don't think it's something the Bukkit team will be able to fix, so instead I'm asking the lot of you for workaround ideas. I've proposed my own (file-based) with its advantage (it works every time) and disadvantage (filename-dependency). Come one, come all :)
     
Thread Status:
Not open for further replies.

Share This Page