Подтвердить что ты не робот

Узнайте, какие классы данного API используются

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

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

4b9b3361

Ответ 1

Вы можете открыть классы, используя 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;
}

Это ваша ответственность, чтобы фактически получить байт-код всех классов.


EDIT с помощью 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

Ответ 4

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

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

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

Ответ 5

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

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

Ответ 6

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

"Couplings View" визуализирует зависимости вашего API в хорошем графике.

Ответ 7

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

alt text

Есть очень хороший быстрый старт:

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

Ответ 8

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