背景
上一篇文章,我们介绍了IDEA插件开发的一些入门知识,本篇文件将结合EE插件的功能,为大家介绍IDEA插件开发中,PSI的使用。
什么是PSI
PSI是Program Structure Interface(程序结构接口)的缩写,官方手册上的解释为IDEA平台中的一个层,负责解析文件并创建支持平台许多功能的语法和语义代码模型。换句话说,就是你想对IDEA中项目的文件进行操作,你就得使用PSI。
PsiFile
PsiFile是文件内所有内容结构的根,所有关于文件内容的操作都是在PsiFile中进行的。
在官方的开发文档中,获取PsiFile对象的方式有5种:
1、从事件获取:
e.getData(LangDataKeys.PSI_FILE);
2、从虚拟文件中获取:
PsiManager.getInstance(project).findFile();
3、从文档获取:
PsiDocumentManager.getInstance(project).getPsiFile();
4、从文件中的元素获取:
psiElement.getContainingFile();
5、从项目中文件名获取:
FilenameIndex.getFilesByName(project, name, scope);
可以通过以下步骤,在IDEA项目中创建文件。
![584b8593a664aff3395b06edf9d88f85.png](https://i-blog.csdnimg.cn/blog_migrate/aa1fa676abe7111d1c96f899c04cede6.jpeg)
此时在IDEA的目录中就能看到一个名称为FileTest.java的文件。
![c23f277aceea28b68c5eeb8b14f4ffb8.png](https://i-blog.csdnimg.cn/blog_migrate/1722d4782a6bf942aec9bd2bf2a522a5.jpeg)
目前只是一个空文件,因为没有导包和类定义相关的内容,所有IDEA将其认定为一个不可编译的Java文件。可以看到它和其他的Java文件相比,图标是不同的。
PsiJavaFile
如果当前PsiFile对象所指定的文件是一个Java文件,则可以将其强转为PsiJavaFile对象。
![5cf84e5ec74f34afb8c3989ae849e37b.png](https://i-blog.csdnimg.cn/blog_migrate/182ea0e57d288a43ac2e1f800ff4a7df.jpeg)
PsiJavaFile对象中包含了文件所在包的声明(PsiPackageStatement)、导入的包列表(PsiImportList)和文件中的类(PsiClass)等内容。如果要对java文件的内容进行修改,则可以操作PsiJavaFile对象。
现在我们创建一个包和类,把它添加到刚才创建的FileTest.java文件中。
![86fdec222aaa3fc0b0b3e5067f8dd92e.png](https://i-blog.csdnimg.cn/blog_migrate/c63bfe56334cb1de12e058d40031e75e.jpeg)
这时文件中就有了包和类的声明,IDEA也将其图标改为了Java类的图标。
![9549390ac83a265175e8d10eab0f680d.png](https://i-blog.csdnimg.cn/blog_migrate/cbed407ae6d71c0123805d817f8bbc9e.jpeg)
PsiClass
如果想对Java文件中类的内容进行操作,可以通过PsiClass对象进行。
我们可以发现一个有趣的现象,当我们把光标放到类的大括号中以及类的注释所在的行,
![28bc987814121425b5ee7be6d37b9433.png](https://i-blog.csdnimg.cn/blog_migrate/7968bb015612a2f4176d8cbd8d6926f3.jpeg)
点击右键-> Generate,就能看到很多关于类的操作。
![b1e9800c6fa8d99c9d88a6fe204a61c6.png](https://i-blog.csdnimg.cn/blog_migrate/9ac5175cb87959c82aa3a58064af3d4c.jpeg)
但如果把光标放到了包路径或导包所在的行,
![cd3942bb608ac144194371e36f0974fe.png](https://i-blog.csdnimg.cn/blog_migrate/79b294aa6af3f69a4f3845765c9dec49.jpeg)
就只剩下了版权这一项操作了。
![53f16a6f18812a8e6c4ea6803efc5494.png](https://i-blog.csdnimg.cn/blog_migrate/e64108d24fca8e2c4fef733f3be81ee7.jpeg)
类的大括号中以及类的注释所在的位置,就是一个PsiClass对象所作用的范围。
在PsiClass中,可以通过以下方式对类中的元素进行操作:
判断该类对象的类型
![c789492e86ce3ff6c6fab655ff9374da.png](https://i-blog.csdnimg.cn/blog_migrate/82d66e38611adbdb5519a071cbd693e9.jpeg)
获取该类对象的信息
![67aa5c5dce75c6470fd4275c2f38ac8f.png](https://i-blog.csdnimg.cn/blog_migrate/f2581f4d5e1b7dd555e3304f98c4cd02.jpeg)
获取类中的元素
![5efaeeb833f612589b00f14c6cbd2b5c.png](https://i-blog.csdnimg.cn/blog_migrate/daf5d6c646fe21ca1ea7583156ab709e.jpeg)
以上只列举了部分PsiClass对象中的方法,下面我们向类中添加元素。
添加注解
给类添加SCFSerializable的注解。(注意:所有对PSI元素的写操作都需要放到WriteCommandAction的runWriteCommandAction()方法中进行)
![f5164a91694211170864d9b6ada93b65.png](https://i-blog.csdnimg.cn/blog_migrate/a58a7ebdc59c1163a4f5cc5829a1f002.jpeg)
代码执行后将得到如下结果。
![dd857f5dc4f11c5e6abe1fba3b8dd45a.png](https://i-blog.csdnimg.cn/blog_migrate/1395306cee89be4bb610d2690e7c9a28.jpeg)
因为没有导入注解所在的包,所有该注解会被IDEA标红报错。可以通过以下方法执行导包操作。
![4493ad55de4c092d848caaeb08b63f29.png](https://i-blog.csdnimg.cn/blog_migrate/772ef6ba821e418ed3a98fe26066ab38.jpeg)
此时就能看到导入的包,IDEA中的编译也不会报错了。
![c7f6c3e6a6f99516adbd2873e2529874.png](https://i-blog.csdnimg.cn/blog_migrate/646565a01441afa383d0d950f6e901b4.jpeg)
PsiField
对象对类中的属性进行操作,可以通过PsiField对象进行。在PsiField中可以通过以下方法获取属性的信息。
![7f2eb8993fa5ed90f9e2d94dfa1aefed.png](https://i-blog.csdnimg.cn/blog_migrate/2d504f5a2f879076dc19a8874bb14c26.jpeg)
添加属性
向类中添加属性,可以通过以下的方式进行。
![231133b4c6c3253ac522dff06b88dd4e.png](https://i-blog.csdnimg.cn/blog_migrate/9a20668da6166a01c364895b9abe6f3a.jpeg)
执行后将向类中新增一个age属性。
![d2179c8cd0463e216171d1af69d22bb6.png](https://i-blog.csdnimg.cn/blog_migrate/95df9df014fa655e2b79f7e1352e6d69.jpeg)
添加注解
可以通过以下方法为属性添加注解。
![480d1a7d00514b53001e3457363df722.png](https://i-blog.csdnimg.cn/blog_migrate/53a8feec143102c9cf0420c54a0fb018.jpeg)
此时将在属性上添加SCFMember注解。
![d18856d7c37a4ea3322883b12cd63adc.png](https://i-blog.csdnimg.cn/blog_migrate/8df60bdd0036e9133183f0c1d79345cd.jpeg)
添加注解参数
如果注解中带有参数,则可以通过PsiAnnotation对象中的setDeclaredAttributeValue()方法为该注解添加参数。
![444d3e7c50175d65f2dbade20521f4ad.png](https://i-blog.csdnimg.cn/blog_migrate/be0ea3588e3a6a9bc661b8552127fd64.jpeg)
此时将在注解中添加orderId的参数。
![93c7a93e969d8e829a1df0dea8feecaa.png](https://i-blog.csdnimg.cn/blog_migrate/d39409f596ff2ab54c403735a08010b7.jpeg)
其中的PSI表达式可以通过以下的方法创建。
![671cafa6e5eff847643041ea445dbc10.png](https://i-blog.csdnimg.cn/blog_migrate/0f8433f08236d1cd52fb044546ab846f.jpeg)
对方法的操作可以通过PsiMethod对象进行,方式和PsiField类似,这里就不重复介绍了。
PsiType
对属性数据类型的操作,可以通过PsiType对象进行。例如属性的默认值,则可以通过PsiType来获取。
基本类型
![8304d43b13ce55c3094009053e3c7d48.png](https://i-blog.csdnimg.cn/blog_migrate/b7ee6813042ab0c8e8ca7b06f1a7cddd.jpeg)
数组类型
![5f0c2e6a953c4f0cfa590f3af072677b.png](https://i-blog.csdnimg.cn/blog_migrate/3727ac7450daca81a59f241daf024774.jpeg)
集合类型
![50e9a57b70341883297ea11070868afa.png](https://i-blog.csdnimg.cn/blog_migrate/6024c16726e0fdc1e257df353820f5af.jpeg)
引用类型,则需要先获取引用类中的所有属性,再获取每个属性的默认值。
![886ccb25cb388208df893cf82cb58856.png](https://i-blog.csdnimg.cn/blog_migrate/4b016525d215562fd44f54e5df21bd81.jpeg)
后记
IDEA插件开发中关于PSI的内容还有很多,由于文章篇幅的限制,以上内容只列举了58EE开发中用到的部分内容。希望能在以后为大家分享更多的IDEA插件开发技术。
作者简介
周德川,工程效率团队后端开发工程师。参与代码文化和工程效率平台建设,对devops技术有相关研究。
相关文章
▪ IDE插件在58研发提效上的实践
欢迎大家关注“58技术”微信公众号,“58技术”是58官方技术号,58技术创新、分享与交流平台。