I like the one-click automatic code generation function of IDEA, such as automatic generation constructor, Get/Set method of field, ToString method and so on. In addition, there are also some plug-ins that provide automatic code generation function, such as the familiar GsonFormat plug-in. Using this plug-in, we can quickly parse json strings to generate a corresponding Java class, which is helpful when interfacing with some third-party apis.
I have written a toolkit for automatically generating classes from JSON at runtime: JSON-class-Generator. Unlike GsonFormat, which generates Java source code, this tool uses ASM to parse the bytecode of the GENERATED classes from the JSON structure tree at runtime. The purpose of writing jSON-class-Generator was to implement a third-party API auto-docking framework, which was not open source because it was related to business.
Although jSON-class-Generator and GsonFormat implement different functions, the principle is similar.
As we learned in the previous article, Java source code has a fixed structure in the Class file generated after compilation. In IDEA, Java source code also has a fixed structure: PSI program structure. Just as we modify a Class file using ASM to manipulate bytecode, we can modify Java code by editing elements of the PSI program structure of a Java source.
If you already know PSI, check out my previous article, Writing an IDEA Plugin: PSI Analysis of Java Source Code.
Automatically generate Java source code
We modeled the automatic code generation function provided by IDEA to Generate… Menu to add a sub menu: GeneratedInvokePayMethod, the plug-in when users click the menu automatically generate a string of code, and the generated code is inserted into the current cursor location.
The first step is to write an Action corresponding to the GeneratedInvokePayMethod menu and implement the actionPerformed method as follows.
public class GeneratedInvokePayMethodAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {}}Copy the code
The actionPerformed method is called when the menu is clicked and takes only one argument:
event
This parameter encapsulates a lot of useful information, such as the value from which we can get the current filePsiFile
Gets the object on which the current cursor fallsPsiElement
And so on.
Second, we need to register the Action and place it in the GenerateGroup of the popup menu. You need to add the following configuration information to the plugin.xml file:
<actions>
<action id="xxx.action.GeneratedInvokePayMethodAction" class="com.xxx.plugin.action.GeneratedInvokePayMethodAction"
text="GeneratedInvokePayMethod">
<! Where to put action -->
<add-to-group group-id="GenerateGroup" anchor="first"/>
</action>
</actions>
Copy the code
The effect is shown below.
Now, we continue to complete GeneratedInvokePayMethodAction actionPerformed method.
Because Intellij Platform does not allow plugins in the main thread of real-time file is written to, can only be done through asynchronous task write, therefore, we need through WriteCommandAction. RunWriteCommandAction to perform a background write operation, See the code below.
public class GeneratedInvokePayMethodAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
// Execute a background task immediately
WriteCommandAction.runWriteCommandAction(editor.getProject(), () -> {
// do ...}); }}Copy the code
To insert a line of code at the current cursor position, we need to do these things:
- 1. Check whether the current file is one
Java
File, with the aid ofactionPerformed
theevent
Parameter to get the current filePsiFile
Example, judgmentPsiFile
Whether the instance is of typePsiJavaFile
If not, it’s not aJava
Code files that don’t need to do anything (or can give a dialog prompt);
// AnActionEvent event
PsiFile psiFile = event.getData(LangDataKeys.PSI_FILE);
Copy the code
- 2. Obtained by step 1
PsiFile
, to find the current cursor positionPsiElement
Instance;
PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset());
Copy the code
Editor.getcaretmodel ().getoffSet () is used to obtain the current cursor position.
Alternatively, you can use the AnActionEvent#getData method to get the PsiElement on which the cursor is currently located as follows:
// AnActionEvent event
PsiElement psiElement = event.getData(LangDataKeys.PSI_ELEMENT);
Copy the code
But this approach does not apply to the current scenario if the cursor is placed on a line of code; After that, the method returns null.
- 3, according to the cursor position
PsiElement
To obtain thePsiElement
Of the methodPsiCodeBlock
There is only one wayPsiCodeBlock
);
PsiElement codeBlock = element;
while(! (codeBlockinstanceof PsiCodeBlock)) {
codeBlock = codeBlock.getParent();
}
Copy the code
- 4. Create a new one
PsiElement
thePsiElement
That’s code that needs to be generated automatically;
For example, to create an expression element (PsiExpression), you can use the PsiElementFactory#createExpressionFromText method as follows.
PsiElement newElement = PsiElementFactory.getInstance(element.getProject())
.createExpressionFromText("Invocation<Object> invocation = Invocation.<Object>builder()\n" +
" .scope(scope)\n" +
" .service(payType)" +
" .operate(\"" + method + "\")\n" +
" .body(merchantNo)\n" +
" .build()", element.getContext());
Copy the code
The PsiElementFactory uses the factory pattern to produce PsiElements and provides a number of apis, such as createField for creating fields, createMethod for creating methods, createClass for creating classes, and createKeyword for creating keywords.
- 5. Finally, the newly created
PsiElement
Add to cursor positionPsiElement
The back of the;
// Parameter 1: the new PsiElement
// Parameter 2: position-referenced PsiElement
codeBlock.addAfter(newElement, element);
Copy the code
The complete sample code is shown below.
public class GeneratedInvokePayMethodAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
WriteCommandAction.runWriteCommandAction(editor.getProject(), () -> {
PsiFile psiFile = event.getData(LangDataKeys.PSI_FILE);
// Find the element on which the cursor is currently resting
PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset());
// Get the PsiCodeBlock element of the current method
PsiElement codeBlock = element;
while(! (codeBlockinstanceof PsiCodeBlock)) {
codeBlock = codeBlock.getParent();
}
// Use PsiElementFactory to create expression elements
PsiElement newElement = PsiElementFactory.getInstance(element.getProject())
.createExpressionFromText("Invocation<Object> invocation = Invocation.<Object>builder()\n" +
" .scope(scope)\n" +
" .service(payType)" +
" .operate(\"" + method + "\")\n" +
" .body(merchantNo)\n" +
" .build()", element.getContext());
// Insert the newly created expression element after the element where the cursor restscodeBlock.addAfter(newElement, element); }); }}Copy the code
Afterword.
The actual implementation of a plug-in may not be that simple. For example, in the UI section not covered in this article, I have omitted some steps: when clicking on a menu, a Dialog will pop up, providing some options, and after completing the options, click OK to regenerate the code.
Writing a plug-in UI is similar to developing an Android app and editing the UI layout, which is understandable if you’ve developed an Android app.