javajar

How to correctly read from JarURLInputStream ? (Java)


I'm trying to read the contents of my own JAR.

With getResourceAsStream(path) I get a sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream. But this stream seems empty, when the path is a directory.

This is my test code:

package de.CoSoCo.testzone;

import java.io.*;

/**
 *
 * @author Ulf Zibis <Ulf.Zibis@CoSoCo.de>
 * @version 
 */
public class ResourceAsStream {

  /**
   * @param args the command line arguments
   */
  public static void main(String[] args) {
    String packagePath = ResourceAsStream.class.getPackageName().replace('.', '/');
    String path;
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    Class c = ResourceAsStream.class;
    InputStream in;
    byte [] bytes = new byte[16];
    try {
      System.out.println("path="+(
          path = packagePath));
      System.out.println("Class="+(
          in = cl.getResourceAsStream(path)));
      BufferedReader r = new BufferedReader(new InputStreamReader(in));
      System.out.println("entries=");
      for (String line; (line = r.readLine()) != null; )
        System.out.println("  "+line);
      in = cl.getResourceAsStream(path);
      System.out.println("read bytes="+in.read(bytes));
    } catch (Exception ex) { System.out.println(ex); }
    try {
      System.out.println("path="+(
          path = packagePath+'/'+"A_Picture.png"));
      System.out.println("Class="+(
          in = cl.getResourceAsStream(path)));
      System.out.println("read bytes="+in.read(bytes));
    } catch (Exception ex) { System.out.println(ex); }
    try {
      System.out.println("path="+(
          path = ""));
      System.out.println("Class="+(
          in = c.getResourceAsStream(path)));
      BufferedReader r = new BufferedReader(new InputStreamReader(in));
      System.out.println("entries=");
      for (String line; (line = r.readLine()) != null; )
        System.out.println("  "+line);
      in = c.getResourceAsStream(path);
      System.out.println("read bytes="+in.read(bytes));
    } catch (Exception ex) { System.out.println(ex); }
    try {
      System.out.println("path="+(
          path = "A_Picture.png"));
      System.out.println("Class="+(
          in = c.getResourceAsStream(path)));
      System.out.println("read bytes="+in.read(bytes));
    } catch (Exception ex) { System.out.println(ex); }
  }
    
}

When I run this with separate class files – e.g. inside my NetBeans IDE – I get the expected directory entries:

path=de/CoSoCo/testzone
Class=java.io.ByteArrayInputStream@4e50df2e
entries=
  A_Picture.png
  ClassFinder.class
  ClassFinder$1.class
  FileChooserDemo.class
  FileTimesFromCalendar.class
  ResourceAsStream.class
read bytes=16
path=de/CoSoCo/testzone/A_Picture.png
Class=java.io.BufferedInputStream@3941a79c
read bytes=16
path=
Class=java.io.ByteArrayInputStream@506e1b77
entries=
  A_Picture.png
  ClassFinder.class
  ClassFinder$1.class
  FileChooserDemo.class
  FileTimesFromCalendar.class
  ResourceAsStream.class
read bytes=16
path=A_Picture.png
Class=java.io.BufferedInputStream@4fca772d
read bytes=16

But when I run it from the JAR it fails:

$ java -jar TestZone.jar
path=de/CoSoCo/testzone
Class=sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@3d4eac69
entries=
read bytes=-1
path=de/CoSoCo/testzone/A_Picture.png
Class=sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@135fbaa4
read bytes=16
path=
Class=sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@330bedb4
entries=
read bytes=-1
path=A_Picture.png
Class=sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@7ea987ac
read bytes=16

To me this looks like a bug, but maybe I'm doing wrong.

My goal is to read the directory entries.


Solution

  • Here is a solution using the StandardJavaFileManager, which finds all resources as well as all classes from a package and it's sub-packages. It works on file system files and JAR packaged as well mixed.

    package de.CoSoCo.testzone;
    
    import java.io.*;
    import java.util.*;
    import javax.tools.*;
    import static javax.tools.JavaFileObject.Kind.*;
    
    /**
     *
     * @author Ulf Zibis <Ulf.Zibis@CoSoCo.de>
     * @version 
     */
    public class ResourceList {
    
      // A sorted set of resource names of this package:
      protected static final Set<String> RESOURCES = new TreeSet<>();
      protected static final Set<Class> CLASSES // A sorted set of subclasses of this package
          = new TreeSet<>((Class o1, Class o2) -> o1.getName().compareTo(o2.getName()));
    
      /**
       * Scans all classes accessible from the context class loader which belong to
       * the current package and subpackages.
       */
      static {
        String packageName = ResourceList.class.getPackageName();
        StandardJavaFileManager fm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
        try {
          for (JavaFileObject file : fm.list(StandardLocation.CLASS_PATH, packageName,
              new TreeSet<>(Arrays.asList(SOURCE, CLASS, HTML, OTHER)), true)) {
            RESOURCES.add(file.getName()
                .replaceAll(".*("+packageName.replace('.', File.separatorChar)+".*?)\\)*$", "$1"));
          }
          for (JavaFileObject file : fm.list(StandardLocation.CLASS_PATH, packageName,
              Collections.singleton(CLASS), true)) {
            try {
              CLASSES.add(Class.forName(file.getName().replace(File.separatorChar, '.')
                .replaceAll(".*("+packageName+".*)\\.class.*", "$1")));
            } catch (ClassNotFoundException | NoClassDefFoundError ex) { System.err.println(ex); }
          }
        } catch (Exception ex) { ex.printStackTrace(); }
      }
    
      /**
       * @param args the command line arguments
       */
      public static void main(String[] args) {
        for (String str : RESOURCES)
          System.out.println("resource name "+str);
        for (Class clazz : CLASSES)
          System.out.println(clazz);
      }
        
    }