Comments 16
Мне известны несколько способов со своими плюсами и минусами:
Есть ещё неплохой способ "Использовать IDE".
А чем этот вариант лучше checkstyle
?
Да, тут не нужно "локально запускать руками checkstyle
", но зато идёт внедрение в процесс сборки что не может его не замедлять — тогда как запуск checkstyle
можно (да и "нужно") привязать к одному из "высоко-уровневых" этапов сборки проекта (например package
у maven
и build
у gradle
) и тогда его уже не нужно будет "запускать" руками и к тому же его можно будет игнорировать для "ускорения" этапов более важных при непосредственной разработке (компиляция).
Приветствую!
Спасибо за статью.
Интересная проблема и решение через обработку аннотаций)
В свое время тоже столкнулся с подобной проблемой поведения AnnotationProcessor
и реализовал через такой же хак, чтобы достучатся до переменных внутри методов в одном пет проекте - https://ololx.github.io/cranberry/cranberry-statement/.
Позвольте мне немного дополнить статью и задать вопрос.
В Java 8+ можно зарегистрировать кастомный TaskListener
через свою реализацию com.sun.source.util.Plugin; В вашем случае это выглядело бы следующим образом:
1 - Написать свою реализацию Plugin
вместо SwitchExhaustiveCheckerProcessor
public class SwitchExhaustiveChecker implements Plugin {
@Override
public String getName() {
return "SwitchExhaustiveCheckerPlugin";
}
@Override
public void init(JavacTask task, String... args) {
task.addTaskListener(new AnalyzeTaskListener());
}
}
2 - Зарегистрировать свой плагин в …/src/main/resources/META-INF/services/com.sun.source.util.Plugin
( аналогично тому, как Вы регистрировали свой процессор)
ru.hixon.switchexhaustivenesschecker.SwitchExhaustiveCheckerPlugin
3 - Для реализации такого плагина потребуется еще указать зависимость на JDK tools в Вашем pom.xml
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
После чего можно использовать Ваш плагин, подключив его к проекту и указав аргумент в maven-compiler-plugin
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${version}</version>
<configuration>
<compilerArgs>
<arg>-Xplugin:SwitchExhaustiveCheckerPlugin</arg>
</compilerArgs>
</configuration>
</plugin>
Это пример подобного плагина, который я смог найти на github
(надеюсь, что авто рне будет против за то, что выложил лине) - https://github.com/moditect/deptective
Вопрос: Рассматривали ли Вы описанный мной способ реализации Вашей задачи? Если - да, то почему остановились на своем варианте - чем он показался Вам выигрышней?
Спасьбо за комментарий.
> Вопрос: Рассматривали ли Вы описанный мной способ реализации Вашей задачи? Если — да, то почему остановились на своем варианте — чем он показался Вам выигрышней?
Честно говоря, не рассматривал, так как не знал / не помнил о такой возможности, в то время как кастомные процессоры аннотаций когда-то делал.
А как в случае с com.sun.source.util.Plugin унзнавать, какие элементы нужно обрабатывать, а какие — нет? Как я понимаю, зарегистрировать свой TaskListener мы легко можем, а вот часть про обработку кастомной аннотации, или чего-то другого, что даст мне список нужных классов для анализа, я пока не вижу.
Благодарю за хороший ответ. Точно также, как это реализовано у Вас сейчас, с небольшими изменениями. Вы создаете новый TaskListener в методе init() Вашего процессора; я лишь хотел показать, что инициализировать TaskListener можно иначе, тогда AbstractProcessor не нужен. К сожалению сейчас не обладаю достаточным временем, чтобы написать что-то конкретное по Вашему примеру. Но, в общих словах:
Сейчас логика поиска нужных элементов по аннотирпованным классам и реализация проверки у Вас реуализована в SwitchExhaustiveCheckerProcessor
и TestMethodTreePathScanner
Можете просто объединить эту логику в одном Вашем TestMethodTreePathScanner
- это конечно очень некрасиво и лучше бы потом отрефакторить и разбить ответственности, но зато позволит быстрее опробовать другой подход.
Далее можно в самом AnalyzeTaskListener
имплементить визитера TestMethodTreePathScanner
к каждому элементу и все должно быть ОК, хотя чую, что с первого раза все не заведется, учитывая что пока предлдожение абстрактное.
class AnalyzeTaskListener implements TaskListener {
private final SwitchExhaustiveCheckerProcessor processor;
AnalyzeTaskListener() {}
@Override
public void started(TaskEvent taskEvent) {
}
@Override
public void finished(final TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.ANALYZE) {
return;
}
CompilationUnitTree compilationUnit = event.getCompilationUnit();
if (compilationUnit == null) {
return;
}
compilationUnit.accept(scanningVisitor, null);
}
}
А в самом плагине уже проинициализировать AnalyzeTaskListener
как писали ранее. Если нужны какие-нибудь утилитные класссы, наподобии тех , что используются у Вас в процессоре или визитере, то можно в классе плагина их проинициализировать и передать через конструктор AnalyzeTaskListener
далее.
Например ``
…
@Override
public void init(JavacTask task, String... args) {
final Context context = ((BasicJavacTask)task).getContext();
Log log = Log.instance(context);
}
...
Тут есть хорошие примеры кода для такого подхода - https://annimon.com/article/2626
Заметил кучу ошибок в примерах кода - не надо было спешить вчера.
Внесу небольшие правки, чтобы в будущем никог оне путать.
class AnalyzeTaskListener implements TaskListener {
private final TestMethodTreePathScanner testMethodTreePathScanner;
AnalyzeTaskListener() {}
@Override
public void started(TaskEvent taskEvent) {
}
@Override
public void finished(final TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.ANALYZE) {
return;
}
CompilationUnitTree compilationUnit = e.getCompilationUnit();
if (compilationUnit == null) {
return;
}
compilationUnit.accept(testMethodTreePathScanner, null);
}
}
Спасибо за статью! Как на русский правильно перевести "switch exhaustiveness"?
Извините, но не знаю, какой верный перевод на русский язык.
Можно ещё вопрос? Вот эта ссылка - https://github.com/openjdk/jdk/blob/master/src/java.xml/share/classes/com/sun/org/apache/bcel/internal/generic/SWITCH.java#L85 - она же не на логику компилятора укзаывает.
Я попробовал поискать по исходникам, но сходу не нашёл никакого другого места с такой же логикой. Так что, пока не могу сказать точно, ошибя я изначально или нет.
Если честно, меня насторожило даде не наименование пакета, а вот эта фраза
компилятор может генерировать фейковые ветки в байткоде, для перфоманс целей
Не очень понял, как создание избыточного кода улучшает производительность.
Как я реализовывал switch exhaustiveness checker для Java 8