Узнайте, какие классы данного API используются - PullRequest
11 голосов
/ 17 сентября 2010

В моем проекте Java я хотел бы программно выяснить, какие классы из данного API используются. Есть ли хороший способ сделать это? Может быть, через разбор исходного кода или разбор байт-кода? Боюсь, что Reflection не принесет никакой пользы.

Для упрощения: нигде в моем проекте нет подстановочных знаков импорта (import com.mycompany.api.*;), нет полностью определенных определений полей или переменных (private com.mycompany.api.MyThingy thingy;) и конструкций Class.forName(...). Учитывая эти ограничения, я думаю, что все сводится к анализу операторов импорта. Есть ли предпочтительный подход для этого?

Ответы [ 8 ]

13 голосов
/ 17 сентября 2010

Вы можете открыть классы, используя ASM * Remapper класс (хотите верьте, хотите нет).Этот класс фактически предназначен для замены всех вхождений имени класса в байт-коде.Однако для ваших целей не нужно ничего заменять.

Это, вероятно, не имеет большого смысла, так что вот пример ...

Во-первых, высоздайте подкласс Remapper, единственная цель которого в жизни - перехватывать все вызовы метода mapType(String), записывая его аргумент для дальнейшего использования.

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;
    }

}

Теперь вы можете написатьметод, подобный следующему:

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;
}

Вы обязаны получить байт-код всех классов.


РЕДАКТИРОВАТЬ с помощью seanizer (OP)

Я принимаю этоответ, но, как приведенный выше код не совсем корректно, я вставлю способ, которым я использовал это:

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;
}

Вот основной класс для тестирования его с помощью:

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());
    }

}

И вотВыход:

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
3 голосов
/ 17 сентября 2010

Я думаю, что вам может помочь следующее:

  1. Анализатор зависимостей класса
  2. Поиск зависимостей
2 голосов
/ 17 сентября 2010
  1. API дерева компиляторов
  2. анализ байтового кода - полные имена должны быть в постоянном пуле
1 голос
/ 17 сентября 2010

Я использую DependencyFinder именно для этой цели.Он может анализировать байт-код и извлекать все зависимости, а затем выводить отчет в формате txt или xml (см. Инструмент DependencyExtractor).Вы должны иметь возможность программно анализировать отчет по коду вашего приложения.

Я интегрировал это в процесс сборки, чтобы убедиться, что определенные API не используются приложением.

1 голос
/ 17 сентября 2010

Что-то вроде этого возможно:

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();
            }
        }
    }
}

Возможно, вы захотите учесть комментарии, но это не должно быть слишком сложно. Вы также можете убедиться, что вы просматриваете только импорт до объявления класса.

0 голосов
/ 18 мая 2012

Спасибо, Адам Пейнтер, Это помогло мне. Но то, что я искал, - это выбор зависимых классов (рекурсивно), что означает использование функции из проектов. Итак, нужно получить все классы, связанные с определенными классами и снова используемые классы этих классов и так далее. а также получить банки. Итак, я создал свой собственный Java Respendency Resolver проект, который найдет зависимые классы / jars для определенного класса в проекте. Я делюсь этим здесь, что может привести к любому использованию какого-либо тела.

0 голосов
/ 17 сентября 2010

Если вы используете Eclipse.Попробуйте использовать инструменты профилирования.Он не только сообщает, какие классы используются, но и намного больше рассказывает об этом.Результаты будут примерно такими:

alt text

Очень хороший быстрый запуск по адресу:

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

0 голосов
/ 17 сентября 2010

Для этого вы можете использовать STAN .

«Представление связей» визуализирует зависимости вашего API в виде симпатичного графика.

...