📜 ⬆️ ⬇️

JavaParser. Lying code easily and naturally

In the world there are many cool little libraries that are not famous, but very useful. The idea is to slowly acquaint Habr with such things. Today I will tell about JavaParser.


JavaParser is a set of tools for parsing, analyzing, transforming and generating Java code. In other words, if you need to take a piece of javakod and somehow conquer it with improvised methods and without the need for special knowledge, this lib is the very thing.


Somewhere in the middle of an article, you SUDDENLY can realize what a nightmare and horror you can create with this, and you can’t wait to finish reading the text and pour me angry comments. Do not hold back, it is not necessary - immediately skrolte to the very bottom and pour out the soul :)




The code is distributed to the githaba under the Apache, LGPL and GPL licenses. The authors have made a relatively decent site for the project, and even filed a small book distributed completely free of charge - which somehow confirms the seriousness of their intentions.


I exchanged a couple of questions with the authors on FOSDEM, the authors leave an impression of smart and adequate people. This article is based on their report .


What does this do? Initially, it turns the Java code into AST (abstract syntax tree) - parsing. Secondly, it can take an already ready AST and turn it into a Java code - unparsing.


What is the ambush, and why do we need libraries at all? Look:


String habraPostText = "  , ,   "; public void writeHabraPost(String habraPostText) { habraPostText = ", .   ."; } public void writeHabraPost() { habraPostText = ", .   ."; } 

For both of these methods, the AST in JavaParser will be the same. A specific node in AST does not know where exactly habraPostText came habraPostText .


Or for example, we will have methods aMethod(int foo) and aMethod(String foo) , inside of which the variable is printed using System.out.println . AST will also be the same.


Therefore, in JavaParser there is a so-called symbol solver , which for each piece of AST calculates specific corresponding pieces of the source code. They have it in a separate project on GitHub called JSS .


There is code that JavaParser can calculate without JSS. For example, if we pull the getter on the field, then the link to the called field is encoded directly into the code. On the other hand, if there is a method in which the return type is somehow cleverly calculated, then here you need to connect heavy artillery, which is JSS. Manually writing such a thing would be very difficult.


Now, why all this may be necessary. For example, you want to automate the generation of garbage code (hello, Lombok!). Or to write a transpiler, which will turn the code in some scripting language invented by you into Java.


The code that does this is very simple. Let's do the class Habrapost:


 CompilationUnit cu = new CompilationUnit(); cu.setPackageDeclaration("ru.habrahabr.hub.java.examples.javaparser"); ClassOrInterfaceDeclaration habraPost = cu.addClass("HabraPost"); habraPost.addField("String", "title"); habraPost.addField("String", "text"); 

And now we add a constructor that forces these fields to be filled:


 habraPost.addConstructor(Modifier.PUBLIC) .addParameter("String", "title") .addParameter("String", "text") .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(), "text"), new NameExpr("text"), AssignExpr.Operator.ASSIGN)))); 

Now we will generate a boilerplate for setters:


 habraPost.addMethod("getTitle", Modifier.PUBLIC).setBody( new BlockStmt().addStatement( new ReturnStmt(new NameExpr("title")))); habraPost.addMethod("getText", Modifier.PUBLIC).setBody( new BlockStmt().addStatement( new ReturnStmt(new NameExpr("text")))); 

And now let's print our class to the console:


 System.out.println(cu.toString()); 

The output will be something like:


 package ru.habrahabr.hub.java.examples.javaparser; public class HabraPost { String title; String text; public HabraPost(String title, String text) { this.title = title; this.text = text; } public void getTitle() { return title; } public void getText() { return text; } } 

In addition, you can immediately do some research code. For example, to study its quality and arrange it as an autotest.


 long wtfs = getNodes(myAPI, MethodDeclaration.class).stream() .filter(m -> m.getParameters().size > 10) .count(); System.out.println(String.format(" ,     : %d", wtfs)); 

I will not bump into complex examples, since everything is visible on the API. The only really unusual feature is that you can call JSS and delve into types. For example, let's find a class that inherits the maximum number of other classes (recursively):


 ResolvedReferenceTypeDeclaration c = getNodes(myAPI, ClassorInterfaceDeclaration).stream() .filter(c -> !c.isInterface()) .map(c -> c.resolve()) // JSS! .sorted(Comparator.comparingInt(o -> -1 * o.getAllAncestors().size))) .findFirst().get(); 

As a result, this lib can be used for automatic refactoring. To do this, you may not have advanced refactoring inside the IDE, but limit yourself solely to the capabilities of JavaParser. Let's do refactoring: replacing an older method call with a newer one.


Remember this story?




Suppose we have a method checkMegamozg(Boolean moderatorInAGoodMood) , which Boomburum runs in its brain 666 times a day. You need to turn it into checkHabrahabr(Boolean moderatorInAGoodMood) .


First we look for the desired method:


 getNodes(myAPI, MethodCallExpr.class).stream() .filter(m -> m.resolveInvokedMethod()). getQialifiedSignature() .equals("ru.habrahabr.Habr.checkMegamozg(java.lang.Boolean)")) .forEach(m -> m.replace(replaceCallsToMegamozg(m))); 

Now how exactly the replacement will look like:


 public MethodCallExpr replaceCallsToMegamozg() { MethodCallExpr newMethodCall = new MethodCallExpr( methodCall.getScope.get(), "checkHabrahabr"); newMethodCall.addArgument(methodCall.getArgument(0)); return newMethodCall; } 

Moreover, if somewhere inside the methods there are comments (hello, lany !), JavaParser tries not to lose them. This is a terribly unpleasant task and the authors are very hovering about this topic.


As you can see, this lib is like a small Swiss knife, simple and relatively reliable. In the future, small features such as the built-in template language will be added so that Java classes can not be generated manually, but by loading them from a file and replacing the ${} .


Look like that's it. Put likes, subscribe, and be sure to tell us what you think about this lib! In addition, you can order me a mini-review of another library - the best offers will turn into habroposta.


Minute advertising. As you probably know, we do conferences. The closest ones are JBreak 2018 and JPoint 2018 . You can come there and chat live with the developers of various modern technologies, for example, there will be Simon Ritter - Deputy CTO from Azul Systems. In the same place you can meet c Victor Gamow from "Debriefing" and a bunch of other interesting people (and you can also cross with idlers like me). In short, come in, we are waiting for you.

')

Source: https://habr.com/ru/post/348710/


All Articles