Как разработчики мы часто с презрением относимся к людям, выполняющим повторяющуюся работу вручную.
Мы должны автоматизировать это , мы думаем.
Тем не менее, мы делаем все действия, связанные с кодированием, вручную. Конечно, мы используем модные интегрированные среды разработки, которые могут выполнить для нас небольшой рефакторинг, но на этом все и заканчивается. Мы не пробуем наше собственное лекарство.
Давайте изменим это. Давайте посмотрим, как мы можем написать код для:
- Генерация скучного, повторяющегося кода Java, который мы должны написать
- Проанализируйте наш код, чтобы ответить на несколько вопросов о нем
- Выполнить некоторую обработку кода и рефакторинг
Хорошо, что мы собираемся достичь всего этого с помощью одного набора библиотек: JavaParser и его младшего брата по имени JavaSymbolSolver.
Начиная
Ну, это легко: просто добавьте JavaSymbolSolver к вашим зависимостям.
Что такое JavaSymbolSolver? Это библиотека, которая дополняет JavaParser, предоставляя ей довольно мощные функции, необходимые для ответа на более сложные вопросы о коде.
JavaSymbolSolver зависит от JavaParser, поэтому вам просто нужно добавить JavaSymbolSolver и Maven или Gradle также получит JavaParser для вас.
Я полагаю, вы знаете, как использовать Maven или Gradle. Если вы этого не сделаете, прекратите читать это и изучите это первым!
Генерация кода с помощью javaparser
Есть несколько ситуаций, в которых вы можете генерировать код Java. Например, вы можете сгенерировать код на основе некоторых внешних данных, таких как схема базы данных или REST API.
Вы также можете перевести другой язык на Java. Например, я разрабатываю DSL для жизни, и пока пользователи видят только те DSL, которые я для них создаю, я часто генерирую Java за кулисами и компилирую его.
Иногда вы хотите просто сгенерировать шаблонный код, как я использовал для dp при работе с JavaEE и всеми этими слоями (кто помнит, как скучно было писать EJB?).
Какой бы ни была ваша причина для генерации кода, вы можете использовать JavaParser. JavaParser не задает вопрос, он просто там, чтобы помочь вам.
Давайте посмотрим, как мы можем сгенерировать класс с двумя полями, конструктором и двумя геттерами. Ничего страшного, но это должно дать вам представление о том, что значит использовать JavaParser для генерации кода.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
CompilationUnit cu = new CompilationUnit(); cu.setPackageDeclaration( "jpexample.model" ); ClassOrInterfaceDeclaration book = cu.addClass( "Book" ); book.addField( "String" , "title" ); book.addField( "Person" , "author" ); book.addConstructor(Modifier.PUBLIC) .addParameter( "String" , "title" ) .addParameter( "Person" , "author" ) .setBody( new BlockStmt() .addStatement( new ExpressionStmt( new AssignExpr( new FieldAccessExpr( new ThisExpr(), "title" ), new NameExpr( "title" ), AssignExpr.Operator.ASSIGN))) .addStatement( new ExpressionStmt( new AssignExpr( new FieldAccessExpr( new ThisExpr(), "author" ), new NameExpr( "author" ), AssignExpr.Operator.ASSIGN)))); book.addMethod( "getTitle" , Modifier.PUBLIC).setBody( new BlockStmt().addStatement( new ReturnStmt( new NameExpr( "title" )))); book.addMethod( "getAuthor" , Modifier.PUBLIC).setBody( new BlockStmt().addStatement( new ReturnStmt( new NameExpr( "author" )))); System.out.println(cu.toString()); |
Эта последняя инструкция напечатает ваш код, свежий и готовый к компиляции. Возможно, вы захотите сохранить код в файл, а не распечатывать его, но вы поняли идею.
Анализ кода с помощью javaparser
Есть много разных вопросов о вашем коде, много разных способов его анализа.
Прежде всего давайте разберем все исходные файлы нашего проекта:
01
02
03
04
05
06
07
08
09
10
|
// Parse all source files SourceRoot sourceRoot = new SourceRoot(myProjectSourceDir.toPath()); sourceRoot.setParserConfiguration(parserConfiguration); List<ParseResult> parseResults = sourceRoot.tryToParse( "" ); // Now get all compilation unitsList allCus = parseResults.stream() .filter(ParseResult::isSuccessful) .map(r -> r.getResult().get()) .collect(Collectors.toList()); |
Давайте также создадим метод для получения всех узлов определенного типа среди всех наших модулей компиляции:
1
2
3
4
5
|
public static List getNodes(List cus, Class nodeClass) { List res = new LinkedList(); cus.forEach(cu -> res.addAll(cu.findAll(nodeClass))); return res; } |
Тогда давайте начнем задавать вопросы, например:
Сколько методов принимают более 3 параметров?
1
2
|
long n = getNodes(allCus, MethodDeclaration. class ) .stream() .filter(m -> m.getParameters().size() > 3 ) .count();System.out.println( "N of methods with 3+ params: " + n); |
Каковы три лучших класса с большинством методов?
1
2
3
|
getNodes(allCus, ClassOrInterfaceDeclaration. class ) .stream() .filter(c -> !c.isInterface()) .sorted(Comparator.comparingInt(o -> - 1 * o.getMethods().size())) .limit( 3 ) .forEach(c -> System.out.println(c.getNameAsString() + ": " + c.getMethods().size() + " methods" )); |
Хорошо, вы поняли идею. Теперь изучите ваш код. Вам нечего скрывать, верно?
Преобразование кода с помощью javaparser
Предположим, вы счастливый пользователь определенной библиотеки. Вы добавили его в свои зависимости несколько лет назад и использовали его с удовольствием. Время прошло, и вы использовали его все больше и больше, в основном во всем проекте.
Однажды выходит новая версия этой полезной библиотеки, и вы решаете, что хотите обновить свои зависимости. Теперь в новой библиотеке они удалили один из методов, которые вы использовали. Конечно, он устарел и был назван oldMethod (который мог бы сказать вам кое-что…).
Теперь старый метод был заменен новым методом . NewMethod принимает 3 параметра: первые два являются такими же, как oldMethod, они просто инвертированы, третий является логическим значением, для которого должно быть установлено значение true, чтобы получить то же поведение, которое мы использовали для oldMethod .
У вас есть сотни звонков на oldMethod … вы собираетесь менять их один за другим? Ну, может быть, если вы заряжаете по часам. Или вы можете просто использовать JavaParser.
Сначала давайте найдем все вызовы старого метода в определенном файле, иначе называемом CompilationUnit в JavaParser parlanse:
1
2
3
4
5
6
|
myCompilationUnit.findAll(ethodCallExpr. class ) .stream() .filter(m -> m.resolveInvokedMethod() .getQualifiedSignature() .equals( "foo.MyClass.oldMethod(java.lang.String, int)" )) .forEach(m -> m.replace(replaceCallsToOldMethod(m))); |
А затем давайте преобразуем старые вызовы в новые:
1
2
3
4
5
6
7
8
|
public MethodCallExpr replaceCallsToOldMethod(MethodCallExpr methodCall) { MethodCallExpr newMethodCall = new MethodCallExpr( methodCall.getScope().get(), "newMethod" ); newMethodCall.addArgument(methodCall.getArgument( 1 )); newMethodCall.addArgument(methodCall.getArgument( 0 )); newMethodCall.addArgument( new BooleanLiteralExpr( true )); return newMethodCall; } |
Круто, теперь нам просто нужно получить код для нашего модифицированного CompilationUnit и просто сохранить его в файле Java.
Долгой жизни новому методу !
Где узнать больше о javaparser
Существует множество возможностей JavaParser, которые мы не видели:
- JavaParser может обрабатывать комментарии, выясняя, к каким элементам они относятся
- JavaParser может выполнять лексическую консервацию или красивую печать : ваш выбор
- Он может выяснить, к какому объявлению метода относится вызов метода, какие предки есть у определенного класса, и многое другое благодаря интеграции с JavaSymbolSolver
- Он может экспортировать AST в JSON, XML, YAML и даже создавать диаграммы, используя Graphviz!
Где вы можете узнать обо всем этом?
Здесь есть несколько ресурсов:
- Мы написали книгу о JavaParser & JavaSymbolSolver, доступную бесплатно. Он называется JavaParser: посетил
- Блог великого Матозоида : он славный разработчик JavaParser, непреодолимой силы, которая выпускает новый релиз каждую неделю. Кто лучше знает о JavaParser?
- Мой скромный блог по языковой инженерии . Я поддерживаю JavaSymbolSolver и пытаюсь помочь в качестве второго в команде JavaParser. Дальняя секунда & # 55357; & # 56898;
- Сайт проекта : на данный момент не очень богатый контент, но мы над этим работаем
- Канал Gitter : у вас есть вопросы? Спросил их там
Резюме
Вряд ли вы научитесь использовать один инструмент для выполнения трех разных задач. Научившись использовать JavaParser, вы сможете анализировать, генерировать и изменять код Java.
Ну, это похоже на Рождество, не так ли?
Смотрите оригинальную статью здесь: JavaParser для генерации, анализа и изменения кода Java Мнения, высказанные участниками Java Code Geeks, являются их собственными. |