12

I need to count the number of compiled classes, interfaces and enums in a given jar file programmatically (so I need three separate numbers). Which API would help me? (I can't use third party libraries.)

I've already tried quite tricky scheme which seems not always correct. Namely, I read each ZipEntry into a byte[] and then feed the result to my custom class loader which extends standard CalssLoader and just sends this byte[] to ClassLoader.defineClass (which is protect and couldn't be called from application code directly). Full code is on the Pastebin.

Artem Pelenitsyn
  • 2,508
  • 22
  • 38
  • Can you call `ZipEntry.getName()` and see if it's a `.class` file? Or do you need to separately count classes and interfaces (and enums?)? – DNA Feb 16 '12 at 16:21
  • I need to count them separately (in addition it is known that not all of the .class-files contain correct byte code). – Artem Pelenitsyn Feb 16 '12 at 16:30
  • The .class-files with incorrect byte code we wish not to count. – Artem Pelenitsyn Feb 16 '12 at 16:37
  • In that case, your existing approach looks good under the circumstances - in what cases is it not working correctly though? – DNA Feb 16 '12 at 16:41
  • I tried it on Apache commons-io.jar (direct link: http://mmcs.sfedu.ru/~ulysses/Misc/commons-io.jar ) There are 76 .class-files there but my code outputs 47 2 0 (classes-interfaces-enums). – Artem Pelenitsyn Feb 16 '12 at 16:53
  • couldnt you construct a classloader with the target as its path and then iterate over the zip entried and use class.forName() to load them? (using the constructed classloader) – radai Feb 16 '12 at 17:24
  • @radai Would you please show in more details how to “construct a classloader with the target as its path”? Google doesn't help and I'm not really good at all this dynamic loading stuff. – Artem Pelenitsyn Feb 16 '12 at 17:42

1 Answers1

14

A jar file is a zip file with a specific pattern. You may use a ZipFile and a ZipEntry or their children classes JarFile and JarEntry.

This code (a method of a custom classloader) will return a Map with arrays of each type of "class" you need.

public Map<String, List<Class<?>>> loadAndScanJar(File jarFile)
        throws ClassNotFoundException, ZipException, IOException {

    // Load the jar file into the JVM
    // You can remove this if the jar file already loaded.
    super.addURL(jarFile.toURI().toURL());

    Map<String, List<Class<?>>> classes = new HashMap<String, List<Class<?>>>();

    List<Class<?>> interfaces = new ArrayList<Class<?>>();
    List<Class<?>> clazzes = new ArrayList<Class<?>>();
    List<Class<?>> enums = new ArrayList<Class<?>>();
    List<Class<?>> annotations = new ArrayList<Class<?>>();

    classes.put("interfaces", interfaces);
    classes.put("classes", clazzes);
    classes.put("annotations", annotations);
    classes.put("enums", enums);

    // Count the classes loaded
    int count = 0;

    // Your jar file
    JarFile jar = new JarFile(jarFile);
    // Getting the files into the jar
    Enumeration<? extends JarEntry> enumeration = jar.entries();

    // Iterates into the files in the jar file
    while (enumeration.hasMoreElements()) {
        ZipEntry zipEntry = enumeration.nextElement();

        // Is this a class?
        if (zipEntry.getName().endsWith(".class")) {

            // Relative path of file into the jar.
            String className = zipEntry.getName();

            // Complete class name
            className = className.replace(".class", "").replace("/", ".");
            // Load class definition from JVM
            Class<?> clazz = this.loadClass(className);

            try {
                // Verify the type of the "class"
                if (clazz.isInterface()) {
                    interfaces.add(clazz);
                } else if (clazz.isAnnotation()) {
                    annotations.add(clazz);
                } else if (clazz.isEnum()) {
                    enums.add(clazz);
                } else {
                    clazzes.add(clazz);
                }

                count++;
            } catch (ClassCastException e) {

            }
        }
    }

    System.out.println("Total: " + count);

    return classes;
}
Eldius
  • 310
  • 2
  • 4
  • 1
    Thanks a lot, it works nearly perfectly. Some minor details: in order to be able to use `super.addURL` you should extend `URLClassLoader` not just `ClassLoader`. And then you have to define constructor which calls `super` with the array of urls. I wish I could feed it with null, but got an exception. So I constructed an array with one URL which corresponds to my jar. – Artem Pelenitsyn Feb 16 '12 at 18:01
  • 1
    You may pass an empty array... This works fine... Sorry about the misunderstood about the super class, this is a UrlClassloader... – Eldius Feb 17 '12 at 00:52