elementui实例代码-编译IDEA插件:手动生成Java代码

关注“Java艺术”,让我们一起成长!

我非常喜欢IDEA的一键手动代码生成功能,比如手动生成构造方法、字段Get/Set方法、ToString方法等。此外,还有一些插件提供了手动代码生成功能,比如就是我们熟悉的GsonFormat插件,使用这个插件可以生成对应的Java类,供我们快速解析json字符串,这在连接一些第三方API时非常有帮助。

作者写了一个运行时根据json手动生成Class的工具包:json-class-generator。 与GsonFormat不同的是,该工具使用ASM解析json结构树的字节码以在运行时生成类,而GsonFormat则生成Java源代码。 当时写json-class-generator的目的是为了实现一个第三方API手动对接框架。 由于该框架涉及业务,因此并未开源。

虽然json-class-generator和GsonFormat实现的功能不同,但是原理是类似的。

在上一篇文章中,我们了解到Java源码编译后生成的Class文件有一个固定的结构,而在IDEA中,Java源码也有一个固定的结构:PSI程序结构。 就像使用 ASM 操作字节码来更改 Class 文件一样,我们也可以通过编辑 Java 源代码的 PSI 程序结构元素来更改 Java 代码。

理解本文的前提是你已经对PSI有一定的了解。 如果你还不了解PSI,可以阅读作者之前写的文章《编写IDEA插件:Java源代码的PSI分析》。

手动生成Java源代码

我们模仿IDEA提供的手动代码生成功能,在右键弹出菜单的Generate...菜单中添加一个子菜单:GeneratedInvokePayMethod。 当插件用户点击这个菜单时,会手动生成一串代码,并将生成的代码插入到当前光标位置。 地点。

首先,需要编译一个与GenerateInvokePayMethod菜单对应的Action,并实现actionPerformed方法。 代码如下。

public class GeneratedInvokePayMethodAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent event) {
}
}

单击菜单时将调用 actionPerformed 方法。 该方法只有一个参数:

其次,我们需要注册Actionelementui实例代码,并将Action放置在右键弹出菜单的GenerateGroup中。 需要在plugin.xml文件中添加以下配置信息:

<actions>
<action id="xxx.action.GeneratedInvokePayMethodAction" class="com.xxx.plugin.action.GeneratedInvokePayMethodAction"
text="GeneratedInvokePayMethod">


<add-to-group group-id="GenerateGroup" anchor="first"/>
</action>
</actions>

疗效如右图所示。

现在我们继续完成GenerateInvokePayMethodAction的actionPerformed技术。

由于IntellijPlatform不允许插件在主线程中进行实时文件写入,因此只能通过异步任务进行写入。 为此,我们需要通过WriteCommandAction.runWriteCommandAction来执行后台写操作,如下代码所示。

public class GeneratedInvokePayMethodAction extends AnAction {

@Override
public void actionPerformed(@NotNull AnActionEvent event) {
// 立即执行一个后台任务
WriteCommandAction.runWriteCommandAction(editor.getProject(), () -> {
// do ...
});
}
}

想要在当前光标位置插入一行代码elementui实例代码,所以我们需要这样做:

// AnActionEvent event
PsiFile psiFile = event.getData(LangDataKeys.PSI_FILE);

PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset());

其中editor.getCaretModel().getOffset()是获取当前光标位置;

另外,还可以使用AnActionEvent#getData来获取当前光标所在的PsiElement,代码如下:

// AnActionEvent event
PsiElement psiElement = event.getData(LangDataKeys.PSI_ELEMENT);

但这些方法并不适用于当前场景。 如果光标放在 ; 旁边一行代码,此方法将返回空值。

PsiElement codeBlock = element;
while (!(codeBlock instanceof PsiCodeBlock)) {
codeBlock = codeBlock.getParent();
}

PsiElement newElement = PsiElementFactory.getInstance(element.getProject())
.createExpressionFromText("Invocation invocation = Invocation.builder()n" +
" .scope(scope)n" +
" .service(payType)" +
" .operate("" + method + "")n" +
" .body(merchantNo)n" +
" .build()", element.getContext());

PsiElementFactory采用鞋工厂模式生产PsiElement,并提供了大量的API,如创建数组的createField、创建方法的createMethod、创建类的createClass、创建关键字的createKeyword等。

// 参数1:新增的PsiElement
// 参数2:位置参照的PsiElement
codeBlock.addAfter(newElement, element);

完整的示例代码如下。

public class GeneratedInvokePayMethodAction extends AnAction {

@Override
public void actionPerformed(@NotNull AnActionEvent event) {
WriteCommandAction.runWriteCommandAction(editor.getProject(), () -> {
PsiFile psiFile = event.getData(LangDataKeys.PSI_FILE);
// 查找当前光标停留在的元素
PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset());
// 获取当前方法的PsiCodeBlock元素
PsiElement codeBlock = element;
while (!(codeBlock instanceof PsiCodeBlock)) {
codeBlock = codeBlock.getParent();
}
// 使用PsiElementFactory创建表达式元素
PsiElement newElement = PsiElementFactory.getInstance(element.getProject())
.createExpressionFromText("Invocation invocation = Invocation.builder()n" +
" .scope(scope)n" +
" .service(payType)" +
" .operate("" + method + "")n" +
" .body(merchantNo)n" +
" .build()", element.getContext());
// 将新创建的表达式元素插入到光标停留在的元素的后面
codeBlock.addAfter(newElement, element);
});
}
}

杂项笔记

事实上,实现一个插件可能并没有那么简单。 比如本文没有介绍的UI部分,作者省略了一些步骤:点击菜单时,会先弹出一个Dialog,提供一些选项,完成选项后点击ok后生成代码。

虽然编辑插件UI与开发Android应用程序并编辑UI布局类似,但如果您开发过Android应用程序,那么理解起来并不困难。

发表评论

发表回复

返回顶部