¿Por qué InputStreamReader lanza un NPE cuando lee desde un jar?

Estoy tratando de iterar sobre los archivos de clase en un directorio conocido usando streams. El objetivo final es obtener los nombres de clase para todas las clases que existen en un paquete en particular, luego cargar las clases en tiempo de ejecución y usar la reflexión para obtener los nombres y valores de todas las constantes estáticas. Esto funciona cuando ejecuto el programa desde la fuente en mi máquina, pero cuando lo ejecuto como un jar, BufferedReader lanza una NPE de ambos ready() y readLine(). Aquí está el código (con el manejo de errores y las mejores prácticas omitidas por brevedad):

private void printClassNamesInPackage(final String strPackage) throws Exception {
    // The returned implementation of InputStream seems to be at fault
    final InputStream       packageStream = getClass().getClassLoader().getResourceAsStream( strPackage );
    final InputStreamReader streamReader  = new InputStreamReader( packageStream );
    final BufferedReader    reader        = new BufferedReader( streamReader );
    // Throws NPE from inside ready() - SEE STACKTRACE BELOW
    // reader.ready()
    String strLine;
    // Throws NPE from inside readLine() -  SEE STACKTRACE BELOW
    while ( null != (strLine = reader.readLine()) ) {
        System.out.println( strLine );
    }
}

El stacktrace de reader.ready():

java.lang.NullPointerException
    at java.io.FilterInputStream.available(FilterInputStream.java:142)
    at sun.nio.cs.StreamDecoder.inReady(StreamDecoder.java:343)
    at sun.nio.cs.StreamDecoder.implReady(StreamDecoder.java:351)
    at sun.nio.cs.StreamDecoder.ready(StreamDecoder.java:165)
    at java.io.InputStreamReader.ready(InputStreamReader.java:178)
    at java.io.BufferedReader.ready(BufferedReader.java:436)

El stacktrace de reader.readLine():

java.lang.NullPointerException
    at java.io.FilterInputStream.read(FilterInputStream.java:116)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
    at java.io.InputStreamReader.read(InputStreamReader.java:167)
    at java.io.BufferedReader.fill(BufferedReader.java:136)
    at java.io.BufferedReader.readLine(BufferedReader.java:299)
    at java.io.BufferedReader.readLine(BufferedReader.java:362)

Pasar por la ejecución revela lo siguiente InputStream implementaciones:

  • De la fuente: java.io.ByteArrayInputStream
  • Desde jar: sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream

Mirando más en JarURLInputStream Encuentro que lo heredado (de FilterInputStream) campo InputStream in is null, lo que conduce a la NPE resultante.

Desafortunadamente, eso es lo más profundo que pude obtener en el depurador.

¿Alguna idea sobre cómo hacer esto correctamente? ¿Qué me estoy perdiendo o haciendo mal? ¡Gracias!

preguntado el 16 de mayo de 11 a las 20:05

Para recapitular, está tratando de obtener una InputStream de una carpeta en lugar de un archivo, ¿verdad? -

¿Es posible "getClass (). GetClassLoader (). GetResourceAsStream (strPackage);" está regresando nulo? -

@BalusC: Sí. La implementación original simplemente creó un new File para strPackage directorio luego usado listFiles() para obtener las clases dentro. Lo estoy refactorizando para usar transmisiones en lugar de File objetos para que podamos admitir la ejecución desde un jar. -

@JustinKSU: No, como puede ver, proporcioné las implementaciones en tiempo de ejecución devueltas por getResourceAsStream. -

Las carpetas no devuelven un InputStream con una lista de todos los archivos o algo así. Usar JarInputStream para extraer el JAR mediante programación. rgagnon.com/javadetails/java-0513.html -

1 Respuestas

Las carpetas no devuelven un InputStream con una lista de todos los archivos o algo así. Usar JarInputStream para extraer el JAR mediante programación. Puedes encontrar un ejemplo aquí. Como referencia, aquí hay un extracto de relevancia ligeramente modificado:

public static List<String> getClassNamesInPackage(String jarName, String packageName) throws IOException {
    JarInputStream jarFile = new JarInputStream(new FileInputStream(jarName));
    packageName = packageName.replace(".", "/");
    List<String> classes = new ArrayList<String>();

    try {
        for (JarEntry jarEntry; (jarEntry = jarFile.getNextJarEntry()) != null;) {
            if ((jarEntry.getName().startsWith(packageName)) && (jarEntry.getName().endsWith(".class"))) {
                classes.add(jarEntry.getName().replace("/", "."));
            }
        }
    } finally {
        jarFile.close();
    }

    return classes;
}

contestado el 17 de mayo de 11 a las 00:05

Gracias por la respuesta, no estaba al tanto de la JarInputStream clase. Esto podría funcionar muy bien, pero me preocupa la necesidad del nombre del jar: ¿por qué no hay una solución que mantenga transparente el hecho de que estoy cargando desde un jar frente al sistema de archivos local? No le he dado una oportunidad a esto todavía, pero les haré saber lo que encuentro. - Nate W.

Existe, pero no de la forma que usted desea. Solo puede hacer referencia a elementos en la ruta de clases si los conoce por su nombre. Luego, el cargador de clases maneja la carga de manera transparente: puede cargarse desde un archivo, un JAR, un conector de red, etc. No existe un método genérico para listar esos elementos. - musiKk

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.