Find out which classes of a given API are used

后端 未结 8 881
鱼传尺愫
鱼传尺愫 2020-11-29 06:20

In a Java Project of mine, I would like to find out programmatically which classes from a given API are used. Is there a good way to do that? Through source code parsing or

相关标签:
8条回答
  • 2020-11-29 06:43
    1. Compiler Tree API
    2. byte code analysis - the fully qualified names should be in the constant pool
    0 讨论(0)
  • 2020-11-29 06:47

    Something like this perhaps:

    import java.io.*;
    import java.util.Scanner;
    import java.util.regex.Pattern;
    
    public class FileTraverser {
    
        public static void main(String[] args) {
            visitAllDirsAndFiles(new File("source_directory"));
        }
    
        public static void visitAllDirsAndFiles(File root) {
            if (root.isDirectory())
                for (String child : root.list())
                    visitAllDirsAndFiles(new File(root, child));
            process(root);
        }
    
        private static void process(File f) {
    
            Pattern p = Pattern.compile("(?=\\p{javaWhitespace}*)import (.*);");
            if (f.isFile() && f.getName().endsWith(".java")) {
                try {
                    Scanner s = new Scanner(f);
                    String cls = "";
                    while (null != (cls = s.findWithinHorizon(p, 0)))
                        System.out.println(cls);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    You may want to take comments into account, but it shouldn't be too hard. You could also make sure you only look for imports before the class declaration.

    0 讨论(0)
  • 2020-11-29 06:52

    You can discover the classes using ASM's Remapper class (believe it or not). This class is actually meant to replace all occurrences of a class name within bytecode. For your purposes, however, it doesn't need to replace anything.

    This probably doesn't make a whole lot of sense, so here is an example...

    First, you create a subclass of Remapper whose only purpose in life is to intercept all calls to the mapType(String) method, recording its argument for later use.

    public class ClassNameRecordingRemapper extends Remapper {
    
        private final Set<? super String> classNames;
    
        public ClassNameRecordingRemapper(Set<? super String> classNames) {
            this.classNames = classNames;
        }
    
        @Override
        public String mapType(String type) {
            classNames.add(type);
            return type;
        }
    
    }
    

    Now you can write a method like this:

    public Set<String> findClassNames(byte[] bytecode) {
        Set<String> classNames = new HashSet<String>();
    
        ClassReader classReader = new ClassReader(bytecode);
        ClassWriter classWriter = new ClassWriter(classReader, 0);
    
        ClassNameRecordingRemapper remapper = new ClassNameRecordingRemapper(classNames);
        classReader.accept(remapper, 0);
    
        return classNames;
    }
    

    It's your responsibility to actually obtain all classes' bytecode.


    EDIT by seanizer (OP)

    I am accepting this answer, but as the above code is not quite correct, I will insert the way I used this:

    public static class Collector extends Remapper{
    
        private final Set<Class<?>> classNames;
        private final String prefix;
    
        public Collector(final Set<Class<?>> classNames, final String prefix){
            this.classNames = classNames;
            this.prefix = prefix;
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public String mapDesc(final String desc){
            if(desc.startsWith("L")){
                this.addType(desc.substring(1, desc.length() - 1));
            }
            return super.mapDesc(desc);
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public String[] mapTypes(final String[] types){
            for(final String type : types){
                this.addType(type);
            }
            return super.mapTypes(types);
        }
    
        private void addType(final String type){
            final String className = type.replace('/', '.');
            if(className.startsWith(this.prefix)){
                try{
                    this.classNames.add(Class.forName(className));
                } catch(final ClassNotFoundException e){
                    throw new IllegalStateException(e);
                }
            }
        }
    
        @Override
        public String mapType(final String type){
            this.addType(type);
            return type;
        }
    
    }
    
    public static Set<Class<?>> getClassesUsedBy(
        final String name,   // class name
        final String prefix  // common prefix for all classes
                             // that will be retrieved
        ) throws IOException{
        final ClassReader reader = new ClassReader(name);
        final Set<Class<?>> classes =
            new TreeSet<Class<?>>(new Comparator<Class<?>>(){
    
                @Override
                public int compare(final Class<?> o1, final Class<?> o2){
                    return o1.getName().compareTo(o2.getName());
                }
            });
        final Remapper remapper = new Collector(classes, prefix);
        final ClassVisitor inner = new EmptyVisitor();
        final RemappingClassAdapter visitor =
            new RemappingClassAdapter(inner, remapper);
        reader.accept(visitor, 0);
        return classes;
    }
    

    Here's a main class to test it using:

    public static void main(final String[] args) throws Exception{
        final Collection<Class<?>> classes =
            getClassesUsedBy(Collections.class.getName(), "java.util");
        System.out.println("Used classes:");
        for(final Class<?> cls : classes){
            System.out.println(" - " + cls.getName());
        }
    
    }
    

    And here's the Output:

    Used classes:
     - java.util.ArrayList
     - java.util.Arrays
     - java.util.Collection
     - java.util.Collections
     - java.util.Collections$1
     - java.util.Collections$AsLIFOQueue
     - java.util.Collections$CheckedCollection
     - java.util.Collections$CheckedList
     - java.util.Collections$CheckedMap
     - java.util.Collections$CheckedRandomAccessList
     - java.util.Collections$CheckedSet
     - java.util.Collections$CheckedSortedMap
     - java.util.Collections$CheckedSortedSet
     - java.util.Collections$CopiesList
     - java.util.Collections$EmptyList
     - java.util.Collections$EmptyMap
     - java.util.Collections$EmptySet
     - java.util.Collections$ReverseComparator
     - java.util.Collections$ReverseComparator2
     - java.util.Collections$SelfComparable
     - java.util.Collections$SetFromMap
     - java.util.Collections$SingletonList
     - java.util.Collections$SingletonMap
     - java.util.Collections$SingletonSet
     - java.util.Collections$SynchronizedCollection
     - java.util.Collections$SynchronizedList
     - java.util.Collections$SynchronizedMap
     - java.util.Collections$SynchronizedRandomAccessList
     - java.util.Collections$SynchronizedSet
     - java.util.Collections$SynchronizedSortedMap
     - java.util.Collections$SynchronizedSortedSet
     - java.util.Collections$UnmodifiableCollection
     - java.util.Collections$UnmodifiableList
     - java.util.Collections$UnmodifiableMap
     - java.util.Collections$UnmodifiableRandomAccessList
     - java.util.Collections$UnmodifiableSet
     - java.util.Collections$UnmodifiableSortedMap
     - java.util.Collections$UnmodifiableSortedSet
     - java.util.Comparator
     - java.util.Deque
     - java.util.Enumeration
     - java.util.Iterator
     - java.util.List
     - java.util.ListIterator
     - java.util.Map
     - java.util.Queue
     - java.util.Random
     - java.util.RandomAccess
     - java.util.Set
     - java.util.SortedMap
     - java.util.SortedSet
    
    0 讨论(0)
  • 2020-11-29 06:57

    I think the following might help you out:

    1. Class Dependency Analyzer
    2. Dependency Finder
    0 讨论(0)
  • 2020-11-29 06:57

    If you use Eclipse. Try using the profiling tools. It doesn't only tells which classes are being used, but tells much more about it. The results will be something like:

    alt text

    There is a very good quickstart at:

    http://www.eclipse.org/tptp/home/documents/tutorials/profilingtool/profilingexample_32.html

    0 讨论(0)
  • 2020-11-29 06:58

    You may want to use STAN for that.

    The "Couplings View" visualizes the dependencies to your API in a nice graph.

    0 讨论(0)
提交回复
热议问题