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 another solution without using the StandardJavaFileManager, which finds all resources as well as all classes from a package and it's sub-packages recursivly. It works on file system files and JAR packaged as well mixed.

    To avoid the Files.isDirectory() check use Files.walkFileTree().

    package de.CoSoCo.testzone;
    
    import java.io.*;
    import java.net.*;
    import java.nio.file.*;
    import java.nio.file.attribute.BasicFileAttributes;
    import java.util.*;
    
    /**
     *
     * @author Ulf Zibis <Ulf.Zibis@CoSoCo.de>
     * @version 
     */
    public class ResourceList {
    
      private static final String PACKAGE = ResourceList.class.getPackageName();
      // A sorted set of resource names of this package:
      protected static final Set<String> RESOURCES = new TreeSet<>();
      // A sorted set of subclasses of this package
      protected static final Set<Class> CLASSES = new TreeSet<>(Comparator.comparing(Class::getName));
    
      /**
       * Scans all resources accessible from the context class loader which belong to
       * the current package and subpackages.
       */
      static {
        try {
          for(Enumeration<URL> res=ResourceList.class.getClassLoader().getResources(PACKAGE.replace('.','/'));
              res.hasMoreElements(); ) {
            URI uri = res.nextElement().toURI();
            try {
              collectResources(Paths.get(uri));
            } catch (FileSystemNotFoundException fe) {
              try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap())) {
                collectResources(Paths.get(uri));
              }
            }
          }
        } catch (Exception ex) { ex.printStackTrace(); }
      }
    
      private static void collectResources(Path path) throws IOException {
        try (var stream = Files.walk(path, FileVisitOption.FOLLOW_LINKS)) {
          stream.forEach(p -> {
            if (Files.isDirectory(p, (LinkOption)null))  return;
            RESOURCES.add(p.toString());
            try {
              String name = p.toString().replace(File.separatorChar, '.').replaceAll(".*(" + PACKAGE + ".*)", "$1");
              if (name.endsWith(".class")) {
                CLASSES.add(Class.forName(name.substring(0, name.length() - ".class".length())));
              }
            } catch (ClassNotFoundException | NoClassDefFoundError ex) { System.err.println(ex); }
          });
        }
      }
    
      // Alternative:
      private static void collectResources2(Path path) throws IOException {
        Files.walkFileTree(path, new SimpleFileVisitor<>() {
          @Override
          public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            RESOURCES.add(file.toString());
            String name = file.toString().replace(File.separatorChar, '.').replaceAll(".*(" + PACKAGE + ".*)", "$1");
            if (name.endsWith(".class"))  try {
              CLASSES.add(Class.forName(name.substring(0, name.length() - ".class".length())));
            } catch (ClassNotFoundException | NoClassDefFoundError ex) { System.err.println(ex); }
            return FileVisitResult.CONTINUE;
          }
        });
      }
    
      /**
       * @param args the command line arguments
       */
      public static void main(String[] args) {
        for (String str : RESOURCES)
          System.out.println("resource path "+str);
        for (Class clazz : CLASSES)
          System.out.println(clazz);
      }
    }