Переходные ссылки в Java

tldr Почему необходимо включать транзитивную ссылку в путь сборки?

Пояснение

Я пытаюсь проанализировать исходный код Java, скомпилированный с помощью рабочей области eclipse. В рабочей области множество проектов.

Я пытаюсь обнаружить неиспользуемые ссылки между проектами.

Мой первый подход заключался в том, чтобы просмотреть все проекты и взять все ссылки из файлов .classpath, а затем проанализировать все файлы .java в том же проекте. Если в файле .java есть оператор импорта из другого проекта, то необходима ссылка на этот другой проект. Таким образом, я нашел несколько ссылок в пути к классам, у которых не было «обоснования».

Но когда я удалил эти ссылки, сборка сломалась. Один из таких случаев, если ссылочный класс расширяет класс из третьего проекта. На этот проект ссылаются транзитивно, и он должен находиться в пути сборки.

Мне интересно, какие другие виды отношений (кроме наследования) приводят к транзитивным ссылкам в пути к классам? А как насчет нескольких уровней наследования, когда каждый класс наследования находится в другом проекте?


person KFleischer    schedule 09.07.2018    source источник
comment
Использует ли ваш проект библиотеки DLL, конфигурацию Spring XML, отражение Java или любой другой аспект, который может помешать достижению этой цели с помощью чистого анализа исходного кода?   -  person ProgrammersBlock    schedule 09.07.2018
comment
Если два класса в двух проектах находятся в одном пакете, вы не найдете между ними зависимости, просто взглянув на операторы импорта.   -  person NickL    schedule 09.07.2018
comment
@ProgrammersBlock Я не понимаю. Я занимаюсь чистым анализом исходного кода.   -  person KFleischer    schedule 10.07.2018
comment
@НикЛ Верно. Это дополнительный случай, который следует учитывать.   -  person KFleischer    schedule 10.07.2018


Ответы (2)


Что делает это необходимым?

Принудительные правила компиляции Java делают это необходимым. Java необходимо знать, что находится в домене кода, чтобы проверить соблюдение правил. Домен — это набор файлов jar в вашем пути к классам. Если вы реализуете интерфейс, неабстрактный реализующий класс или неабстрактные дочерние классы, реализующие интерфейс, должны реализовать этот интерфейс. Аннотации переопределения должны быть удовлетворены. Ссылки на другие методы должны быть видны как с точки зрения кода, так и с точки зрения человека. Если метод использует метод другого класса или интерфейса, сигнатура метода должна быть известна вызывающему классу.

Как мне подойти к этому?

Я буду использовать «классификаторы» для обозначения как классов, так и интерфейсов. Вы хотите начать с набора известных проверенных классификаторов и известных непроверенных классификаторов.
Начните с известных непроверенных классификаторов — это все классификаторы в вашем целевом проекте, а известные проверенные классификаторы пусты.

Просмотрите каждый из непроверенных классификаторов и определите любые используемые классификаторы, которые еще не известны, и добавьте их в набор непроверенных классификаторов.

Убедитесь, что текущий непроверенный классификатор находится в пути к классам. Ошибка, если не найдено.

Переместите обработанный классификатор из непроверенного в проверенный.

Повторяйте этот процесс, пока все классификаторы не будут проверены.

Используемые классификаторы включают типы полей, возвращаемые типы методов, типы параметров методов, типы переменных в параметрах, расширенные классы, реализованные интерфейсы, аннотации и, в некоторых случаях, ссылки на Javadoc. В зависимости от правил вашего проекта, при использовании исключительно импорта некоторые вещи могут быть упущены. Интерфейс может быть в том же пакете, но получен из другого проекта.

Почему отражение и Spring-конфигурация должны выходить за рамки?

Такие действия используют текстовые передачи для выполнения кода. Отражение может создавать классы, используя строку «Package.classname», не идентифицируясь как «используемый класс». Эти концепции потребуют дополнительной обработки, выходящей за рамки чистого анализа исходного кода или анализа байт-кода.

Если вы хотите погрузиться глубже, спецификация виртуальной машины Java может дать вам более глубокое понимание того, что необходимо для запуска файла класса. В частности, «4. Формат файла класса» в спецификации Java 10 и «5. Загрузка, связывание и инициализация». https://docs.oracle.com/javase/specs/

person ProgrammersBlock    schedule 11.07.2018
comment
Вспоминая об этом, используемым классификаторам также может потребоваться каждый вызов метода внутри реализации класса, потому что файл класса записывает исходный классификатор в байт-коде. Это означает, что если у вас есть A.b().c().d(); тогда также необходимо учитывать возвращаемый тип b() и c(), даже если для них не создается ни переменная, ни поле. - person ProgrammersBlock; 12.07.2018
comment
Спасибо за этот алгоритм. Но я хотел знать, какие случаи приводят к транзитивным зависимостям (или необъявленным операторам импорта), поскольку я не хотел создавать компилятор. - person KFleischer; 18.07.2018

Данный

  • ProjectA с ClassA, который ссылается на ClassB из Project B
  • третий ProjectC с ClassC

В этих случаях вам нужно ссылаться на ProjectC из проектаA

  • если ClassB наследует/реализует от classC и ClassA вызывает метод от ClassB.
  • если ClassA вызывает (!) метод из ClassB, который имеет сигнатуры, содержащие ClassC (параметр или возвращаемое значение) (это также верно для исключений!)

Кроме того, не все ссылки ProjectA можно найти, просмотрев операторы импорта ClassA.

  • Если ClassC используется ClassA и находится в том же пакете, что и ClassA, ClassC не будет указан как импортируемый в ClassA.
  • Если ClassC используется ClassA по его полному имени (включая имя пакета), то оператор импорта не создается.
person KFleischer    schedule 10.07.2018