2

I would like to analyse .class files and get information about which class uses which other class.

jdeps is a command line tool which allows you to display some information in the console, but I would like to avoid calling an external tool and scraping the command line output.

J Fabian Meier
  • 33,516
  • 10
  • 64
  • 142
  • and you can't do that from an IDE? Just wondering... also seems like http://commons.apache.org/proper/commons-bcel/ could do that, but I have not tried – Eugene Apr 25 '18 at 09:52
  • Thank you for the hint. This is about analysing 50000 Java artifacts programmatically. – J Fabian Meier Apr 25 '18 at 09:56
  • AFAIK `javap` has not programatic access, but of course you could use a process for that and invoke it and then parse output; it seems that simply a `grep` would be enough in your case to simply search for that class. I just wonder if there would be any issues with inheritance though... I like the question – Eugene Apr 25 '18 at 10:00
  • 1
    Take a look into [asm](http://download.forge.ow2.org/asm/asm4-guide.pdf) – Victor Gubin Apr 25 '18 at 10:02
  • https://stackoverflow.com/questions/1753337/determine-the-classes-referenced-in-a-class-file – Arne Deutsch Apr 26 '18 at 10:56

1 Answers1

6

All dependencies are recorded at a central place of a class file, the constant pool. So to efficiently process all dependencies, you need a library allowing to process the constant pool without looking at the rest of the class file (which rules out ASM, which is otherwise a very good bytecode processing library).

So using, e.g. Javassist, you can do the job as

private static Set<String> getDependencies(InputStream is) throws IOException {
    ClassFile cf = new ClassFile(new DataInputStream(is));
    ConstPool constPool = cf.getConstPool();
    HashSet<String> set = new HashSet<>();
    for(int ix = 1, size = constPool.getSize(); ix < size; ix++) {
        int descriptorIndex;
        switch (constPool.getTag(ix)) {
            case ConstPool.CONST_Class: set.add(constPool.getClassInfo(ix));
            default: continue;
            case ConstPool.CONST_NameAndType:
                descriptorIndex = constPool.getNameAndTypeDescriptor(ix);
                break;
            case ConstPool.CONST_MethodType:
                descriptorIndex = constPool.getMethodTypeInfo(ix);
        }
        String desc = constPool.getUtf8Info(descriptorIndex);
        for(int p = 0; p<desc.length(); p++)
            if(desc.charAt(p)=='L')
                set.add(desc.substring(++p, p = desc.indexOf(';', p)).replace('/', '.'));
    }
    return set;
}

Testing it with

try(InputStream is = String.class.getResourceAsStream("String.class")) {
    set = getDependencies(is);
}
set.stream().sorted().forEachOrdered(System.out::println);

shows

[B
[C
[I
[Ljava.lang.CharSequence;
[Ljava.lang.String;
java.io.ObjectStreamField
java.io.Serializable
java.io.UnsupportedEncodingException
java.lang.AbstractStringBuilder
java.lang.CharSequence
java.lang.Character
java.lang.Comparable
java.lang.Double
java.lang.Float
java.lang.IndexOutOfBoundsException
java.lang.Integer
java.lang.Iterable
java.lang.Long
java.lang.Math
java.lang.NullPointerException
java.lang.Object
java.lang.OutOfMemoryError
java.lang.String
java.lang.String$1
java.lang.String$CaseInsensitiveComparator
java.lang.StringBuffer
java.lang.StringBuilder
java.lang.StringCoding
java.lang.StringCoding$Result
java.lang.StringIndexOutOfBoundsException
java.lang.StringLatin1
java.lang.StringLatin1$CharsSpliterator
java.lang.StringUTF16
java.lang.StringUTF16$CharsSpliterator
java.lang.StringUTF16$CodePointsSpliterator
java.lang.System
java.lang.Throwable
java.lang.Void
java.nio.charset.Charset
java.util.ArrayList
java.util.Arrays
java.util.Comparator
java.util.Formatter
java.util.Iterator
java.util.List
java.util.Locale
java.util.Objects
java.util.Spliterator
java.util.Spliterator$OfInt
java.util.StringJoiner
java.util.regex.Matcher
java.util.regex.Pattern
java.util.stream.IntStream
java.util.stream.StreamSupport

(on Java 9)


You can get to the same result using BCEL:

private static Set<String> getDependencies(InputStream is) throws IOException {
    JavaClass cf = new ClassParser(is, "").parse();
    ConstantPool constPool = cf.getConstantPool();
    HashSet<String> set = new HashSet<>();
    constPool.accept(new DescendingVisitor(cf, new EmptyVisitor() {
        @Override public void visitConstantClass(ConstantClass cC) {
            set.add(((String)cC.getConstantValue(constPool)).replace('/', '.'));
        }
        @Override public void visitConstantNameAndType(ConstantNameAndType cNaT) {
            processSignature(cNaT.getSignature(constPool));
        }
        @Override public void visitConstantMethodType(ConstantMethodType cMt) {
            processSignature(
                constPool.constantToString(cMt.getDescriptorIndex(),
                (byte)ConstPool.CONST_Utf8));
        }
        private void processSignature(String desc) {
            for(int p = 0; p<desc.length(); p++)
                if(desc.charAt(p)=='L')
                    set.add(desc.substring(++p, p=desc.indexOf(';', p)).replace('/', '.'));
        }
    }));
    return set;
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I was really really hoping you would answer this one! – Eugene Apr 26 '18 at 10:13
  • Thank you. What do the first five entries (with the [ at the beginning) mean? – J Fabian Meier Apr 26 '18 at 11:08
  • 2
    @JFMeier these names are in the [“Binary names”](https://docs.oracle.com/javase/9/docs/api/java/lang/ClassLoader.html#name) format, whereas `[B`, `[C`, `[I` denote `byte[]`, `char[]`, and `int[]`. `[Ljava.lang.CharSequence;` and `[Ljava.lang.String;` refer to `CharSequence[]` and `String[]`. Basically, it’s the JVM internal name, but `/` replaced with `.`. – Holger Apr 26 '18 at 11:11
  • I can't get either of these to work. The current versions of those libraries have changed too much for me to adapt this code in the 40 minutes I've spent at it. – Joseph Jaquinta Nov 02 '22 at 20:38
  • 1
    @JosephJaquinta you can use the solution of [this answer](https://stackoverflow.com/a/19928470/2711488) which doesn’t depend on other libraries, hence, can’t become obsoleted by library changes. – Holger Nov 03 '22 at 12:11