前言
本篇实战制作一个自动生成 Android SQLite 数据库代码的插件,建议先看看”Android Studio Plugin —— 插件开发的基本流程“这篇文章。它是今天这篇文章的基础。
一、主要 API 介绍
1. Virtual File
虚拟文件类,可以当做 Java 开发中的 File 对象理解,概念比较类似。
获取方法:
通过 Action 获取: event.getData(PlatformDataKeys.VIRTUAL_FILE)。
通过本地文件路径获取: LocalFileSystem.getInstance().findFileByIoFile()。
通过 PSI file 获取: psiFile.getVirtualFile()。
通过 document 获取: FileDocumentManager.getInstance().getFile()。
用处
传统的文件操作方法这个对象都支持,比如获取文件内容,重命名,移动,删除等。
2. PSI File
PSI系统下的文件类。
获取方法
- 通过 Action 获取: e.getData(LangDataKeys.PSI_FILE)。
- 通过 VirtualFile 获取: PsiManager.getInstance(project).findFile()。
- 通过 document 获取: PsiDocumentManager.getInstance(project).getPsiFile()。
- 通过文件中的 Element 元素获取: psiElement.getContainingFile(), 如果要通过名字获取,请使用 FilenameIndex.getFilesByName(project, name, scope)。
用处
作为 PSI 系统中的一个元素,可以使用 PSI Element 的各种具体方法。
二、定位到需要创建文件的目录
我们把生成的类都存放在包名目录下面的 db 文件夹中( {package}.ab )。
首先需要获取到包名目录路径 .../app/src/main/java/{package},然后才能在它下面新建 db 文件夹,包名目录可以通过 AndroidManifest.xml 文件获取。
为了方便测试,我们在 IntelliJ IDEA 中新建一个 PluginDemo 工程,查看 PluginDemo 工程的 app Module 的 AndroidManifest.xml文件,如下图所示:
获取 AndroidManifest.xml 文件,代码如下所示:
/**
* 获取 AndroidManifest.xml 文件
* @param project
* @return
*/
public static PsiFile getAndroidManifestFile(Project project) {
// AndroidManifest.xml文件的路径
String path = project.getBasePath() + File.separator +
"app" + File.separator +
"src" + File.separator +
"main" + File.separator +
"AndroidManifest.xml";
VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path);
if (virtualFile == null) {
return null;
}
// 获取到 VirtualFile 后再转换成 PsiFile,大部分操作都是针对Psi体系的.
return PsiManager.getInstance(project).findFile(virtualFile);
}
project 可以在 Action 中通过 e.getData(PlatformDataKeys.PROJECT) 获取,代码如下所示:
@Override
public void actionPerformed(AnActionEvent e) {
// 获取 Project
Project project = e.getData(PlatformDataKeys.PROJECT);
}
然后解析 AndroidManifest.xml 文件,获取 package 属性里的包名,代码如下所示:
/**
* 解析 AndroidManifest.xml ,获取 package 属性值
*
* @param project
* @return
*/
public static String getAppPackageNameDir(Project project) {
PsiFile manifestFilePsiFile = getAndroidManifestFile(project);
XmlDocument xml = (XmlDocument) manifestFilePsiFile.getFirstChild();
return xml.getRootTag().getAttributeValue("package");
}
再根据包名获取到包名目录,代码如下所示:
/**
* 获取包名目录文件
*
* @param project
* @return
*/
public static VirtualFile getAppPackageBaseDir(Project project) {
String path = project.getBasePath() + File.separator +
"app" + File.separator +
"src" + File.separator +
"main" + File.separator +
"java" + File.separator +
getAppPackageNameDir(project).replace(".", File.separator);
return LocalFileSystem.getInstance().findFileByPath(path);
}
最后,判断包名目录下面是否存在 db 文件夹,如果不存在就创建一个,注意,此方法需在子线程中调用,后面有说明,代码如下所示:
/**
* 获取包名目录下面的 db 文件
*
* @return
*/
public static VirtualFile getDbFile(Project project) {
VirtualFile baseDir = getAppPackageBaseDir(project);
VirtualFile dbDir = baseDir.findChild("db");
if (dbDir == null) {
// 如果不存在就创建一个
try {
dbDir = baseDir.createChildDirectory(null, "db");
} catch (IOException e) {
e.printStackTrace();
}
}
return dbDir;
}
三、解析实体类
我们可以用 action 中的 event 获取当前正在编辑的文件,然后在 File 中获取到 PsiClass 元素,最后遍历 Class 获取全部成员变量 Field 。PsiClass 和 Java 中的 Class 相似。
创建 UserInfo 类,如下图所示:
获取当前正在编辑的文件 UserInfo.java ,然后解析 UserInfo 的成员变量,代码如下所示:
/**
* 解析实体
*
* @param event
* @return object[0] 实体类对应的PsiClass ,objects[1] 对应实体的包名 ,object[2] 对应实体的成员变量的map集合
*/
public static Object[] parseEntry(AnActionEvent event) {
Object[] objects = new Object[3];
/*
key 为属性变量的名称 value 为属性变量的类型
*/
Map<String, String> map = new HashMap<>();
PsiFile file = event.getData(PlatformDataKeys.PSI_FILE);
for (PsiElement psiElement : file.getChildren()) {
if (psiElement instanceof PsiClass) {
// 获取到当前正在编辑的类
PsiClass clazz = (PsiClass) psiElement;
objects[0] = clazz;
// 获取类的所有成员变量,这种获取方式有点类似于java的反射。
PsiField[] allFields = clazz.getAllFields();
for (PsiField p : allFields) {
// 成员变量的类型
String type = p.getType().getPresentableText();
// 成员变量的名称
String name = p.getName();
map.put(name, type);
}
} else if (psiElement instanceof PsiPackageStatement) {
// 获取到当前正在编辑的类的包信息
PsiPackageStatement packageStatement = (PsiPackageStatement) psiElement;
String packageName = packageStatement.getPackageName();
objects[1] = packageName;
}
}
objects[2] = map;
return objects;
}
说明 : 上述代码 for 循环中 psiElement 的值有如下几种情况
(1)PsiPackageStatement:xxx
包信息:xxx 代表当前编辑的类所在的包
(2)PsiWhiteSpace**
空白行信息
(3)PsiImportList
导包信息
(4)PsiClass:XXX
类名信息,XXX 代表当前编辑的类的名称
四、生成代码
1 生成DatabaseHelper.java
注意:生成代码与创建文件都必须要在子线程中执行,请看下面的测试代码调用过程。
代码下所示:
DBGenerator.java
/**
* 生成 DatabaseHelper.java 文件
* priKeyField 表示用户设置的主键
*/
public static void generatorDbHelperFile(Project project, PsiClass psiClass, Map<String, String> map, PsiField priKeyField, VirtualFile dbDir) {
String fileName = "DatabaseHelper.java";
VirtualFile child = dbDir.findChild(fileName);
if (child == null) {
// 没有就创建一个,第一次使用代码字符串创建一个类
PsiFile initFile = PsiFileFactory.getInstance(project).createFileFromText(fileName, JavaFileType.INSTANCE,
CodeFactory.genSQLiteOpenHelperInitCode(project));
// 添加到db目录
PsiManager.getInstance(project).findDirectory(dbDir).add(initFile);
child = dbDir.findChild(fileName);
}
PsiFile psiFile = PsiManager.getInstance(project).findFile(child);
// 用拼接的代码生成 create table 方法
String createTableCode = CodeFactory.genCreateTableCode(psiClass, map, priKeyField);
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiMethod createTableMethod = factory.createMethodFromText(createTableCode, psiFile);
// 获取DatabaseHelper类的PsiClass
PsiClass fileClass = PluginUtils.getFileClass(psiFile);
// 将创建的method添加到DatabaseHelper Class中
fileClass.add(createTableMethod);
// 获取DatabaseHelper类的onCreate方法
PsiMethod onCreateMethod = fileClass.findMethodsByName("onCreate", false)[0];
// 在DatabaseHelper类中的onCreate方法里,添加create table方法的调用语句
onCreateMethod.getBody().add(factory.createStatementFromText(createTableMethod.getName() + "(db);", fileClass));
}
CodeFactory.java
/**
* 生成 DatabaseHelper 初始代码
*
* <pre>
* package {package}.db;
*
* import android.content.Context;
* import android.database.sqlite.SQLiteDatabase;
* import android.database.sqlite.SQLiteOpenHelper;
*
* public class DatabaseHelper extends SQLiteOpenHelper {
* private static String DB_NAME = "INPUT YOUR DB FILE NAME";
* private static final int DB_VERSION = 1;
*
* private static volatile DatabaseHelper instance = null;
*
* public static DatabaseHelper getInstance(Context context) {
* if (instance == null) {
* synchronized (DatabaseHelper.class) {
* if (instance == null) {
* instance = new DatabaseHelper(context);
* }
* }
* }
* return instance;
* }
*
* private DatabaseHelper(Context context) {
* super(context, DB_NAME, null, DB_VERSION);
* }
*
* @Override
* public void onCreate(SQLiteDatabase db) {
* }
*
* @Override
* public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
* }
* }
* </pre>
*/
public static String genSQLiteOpenHelperInitCode(Project project) {
return StringUtils.formatSingleLine(0, "package " + AndroidFileUtils.getAppPackageNameDir(project) + ".db;") +
"\n" +
StringUtils.formatSingleLine(0, "import android.content.Context;") +
StringUtils.formatSingleLine(0, "import android.database.sqlite.SQLiteDatabase;") +
StringUtils.formatSingleLine(0, "import android.database.sqlite.SQLiteOpenHelper;") +
"\n" +
StringUtils.formatSingleLine(0, "public class DatabaseHelper extends SQLiteOpenHelper {") +
StringUtils.formatSingleLine(1, "private static String DB_NAME = \"INPUT YOUR DB FILE NAME\";") +
StringUtils.formatSingleLine(1, "private static final int DB_VERSION = 1;") +
StringUtils.formatSingleLine(1, "private static volatile DatabaseHelper instance = null;") +
"\n" +
StringUtils.formatSingleLine(1, "public static DatabaseHelper getInstance(Context context) {") +
StringUtils.formatSingleLine(2, "if (instance == null) {") +
StringUtils.formatSingleLine(3, "synchronized (DatabaseHelper.class) {") +
StringUtils.formatSingleLine(4, "if (instance == null) {") +
StringUtils.formatSingleLine(5, "instance = new DatabaseHelper(context);") +
StringUtils.formatSingleLine(4, "}") +
StringUtils.formatSingleLine(3, "}") +
StringUtils.formatSingleLine(2, "}") +
StringUtils.formatSingleLine(2, "return instance;") +
StringUtils.formatSingleLine(1, "}") +
"\n" +
StringUtils.formatSingleLine(1, "private DatabaseHelper(Context context) {") +
StringUtils.formatSingleLine(2, "super(context, DB_NAME, null, DB_VERSION);") +
StringUtils.formatSingleLine(1, "}") +
"\n" +
StringUtils.formatSingleLine(1, "@Override") +
StringUtils.formatSingleLine(1, "public void onCreate(SQLiteDatabase db) {") +
StringUtils.formatSingleLine(1, "}") +
"\n" +
StringUtils.formatSingleLine(1, "@Override") +
StringUtils.formatSingleLine(1, "public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {") +
StringUtils.formatSingleLine(1, "}") +
"}";
}
/**
* 生成创建数据库表单方法代码
* 表名根据实体类名转化而来(实体驼峰命令转化为下划线命名规则,例如UserInfo -> user_info)
* 如果没有设置主键,默认增加id属性,为主键
* 其他属性名为实体类的成员变量名称
* <pre>
* String sql = "CREATE TABLE IF NOT EXISTS "
* "user_info"
* + "("
* + "_id INTEGER PRIMARY KEY AUTOINCREMENT,"
* + "name TEXT,"
* + "age TEXT,"
* + "isMan TEXT"
* + "address TEXT"
* + "tel TEXT"
* + ")";
* </pre>
*/
public static String genCreateTableCode(PsiClass psiClass, Map<String, String> map, PsiField priKeyField) {
String tableName = StringUtils.camelToUnderline(psiClass.getName());
StringBuilder sb = new StringBuilder();
sb.append(StringUtils.formatSingleLine(0, "public void create" + psiClass.getName() + "Table(SQLiteDatabase db) {"));
sb.append(StringUtils.formatSingleLine(1, "String sql = \"CREATE TABLE IF NOT EXISTS\""));
sb.append(StringUtils.formatSingleLine(3, "+ \" " + tableName + " (\""));
if (priKeyField == null) {
// 默认主键
sb.append(StringUtils.formatSingleLine(3, "+ \" _id INTEGER PRIMARY KEY AUTOINCREMENT,\""));
} else {
sb.append(StringUtils.formatSingleLine(3, "+ \" _id INTEGER,\""));
}
for (String key : map.keySet()) {
if (priKeyField != null && priKeyField.getName().equals(key)) {
// 有自定义主键
sb.append(StringUtils.formatSingleLine(3, "+ \" " + key + " TEXT PRIMARY KEY,\""));
} else {
sb.append(StringUtils.formatSingleLine(3, "+ \" " + key + " TEXT ,\""));
}
}
sb.replace(sb.lastIndexOf(",\""), sb.lastIndexOf(",\"") + 2, "\"\n\t\t+ \")\";");
sb.append(StringUtils.formatSingleLine(1, "db.execSQL(sql);"));
sb.append(StringUtils.formatSingleLine(0, "}"));
return sb.toString();
}
StringUtils.java
/**
* 将String按需要格式化,前面加缩进符,后面加换行符
*
* @param tabNum 缩进量
* @param srcString
* @return
*/
public static String formatSingleLine(int tabNum, String srcString) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < tabNum; i++) {
sb.append("\t");
}
sb.append(srcString);
sb.append("\n");
return sb.toString();
}
/**
* 驼峰命名转下划线命名
* @param src
* @return
*/
public static String camelToUnderline(String src) {
StringBuilder sb = new StringBuilder();
StringBuilder sbWord = new StringBuilder();
char[] chars = src.trim().toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if(c >= 'A' && c <= 'Z') {
// 一旦遇到大写单词,保存之前已有字符组成的单词
if(sbWord.length() > 0) {
if(sb.length() > 0) {
sb.append("_");
}
sb.append(sbWord.toString());
}
sbWord = new StringBuilder();
}
sbWord.append(c);
}
if(sbWord.length() > 0) {
if(sb.length() > 0) {
sb.append("_");
}
sb.append(sbWord.toString());
}
return sb.toString().toLowerCase();
}
编写测试代码:
@Override
public void actionPerformed(AnActionEvent e) {
// 获取 Project
Project project = e.getData(PlatformDataKeys.PROJECT);
// 注意:生成代码与创建文件必须要在子线程中执行
WriteCommandAction.runWriteCommandAction(project, () -> {
// 获取db目录
VirtualFile dbDir = AndroidFileUtils.getDbFile(project);
// 获取当前正在编辑类的的所有成员遍历集合
Object[] objects = ParseEntryUtils.parseEntry(e);
if (objects == null || objects.length != 3) {
return;
}
PsiClass psiClass = (PsiClass) objects[0];
String entryPackageName = (String) objects[1];
Map<String, String> map = (Map<String, String>) objects[2];
if (psiClass == null || map == null || map.size() == 0) {
return;
}
DBGenerator.generatorDbHelperFile(project, psiClass, map,
null, dbDir);
});
}
测试结果如下图所示:
2 生成UserInfoDao.java
UserIinfoDao 中主要是对数据库的增、删、改、查等方法,现在我们只实现增加和查询的方法,其他的方法类似。
DBGenerator.java
/**
* 生成 XXXDao.java 文件
*/
public static void generatorDaoCodeFile(Project project, PsiClass psiClass, String entryPackageName, Map<String, String> map, PsiFile priKeyField, VirtualFile dbDir) {
String fileName = psiClass.getName() + "Dao.java";
// 使用代码字符串创建个类
PsiFile initFile = PsiFileFactory.getInstance(project).createFileFromText(
fileName, JavaFileType.INSTANCE, CodeFactory.genDaoCode(project, psiClass,entryPackageName, map, priKeyField));
// 加到db目录下
PsiManager.getInstance(project).findDirectory(dbDir).add(initFile);
}
CodeFactory.java
/**
* 生成XXXDao.java ,增删改查方法。
*/
public static String genDaoCode(Project project, PsiClass psiClass, String entryPackageName, Map<String, String> map, PsiFile priKeyField) {
String daoClassName = psiClass.getName() + "Dao";
StringBuilder sb = new StringBuilder();
sb.append(StringUtils.formatSingleLine(0, "package " + AndroidFileUtils.getAppPackageNameDir(project) + ".db;"));
sb.append("\n");
sb.append(StringUtils.formatSingleLine(0, "import android.content.ContentValues;"));
sb.append(StringUtils.formatSingleLine(0, "import android.database.Cursor;"));
sb.append(StringUtils.formatSingleLine(0, "import android.database.sqlite.SQLiteDatabase;"));
sb.append(StringUtils.formatSingleLine(0, "import android.database.sqlite.SQLiteStatement;"));
sb.append(StringUtils.formatSingleLine(0, "import android.content.Context;"));
sb.append(StringUtils.formatSingleLine(0, "import " + entryPackageName + "." + psiClass.getName() + ";"));
sb.append("\n");
sb.append(StringUtils.formatSingleLine(0, "import java.util.ArrayList;"));
sb.append(StringUtils.formatSingleLine(0, "import java.util.List;"));
sb.append("\n");
sb.append(StringUtils.formatSingleLine(0, "public class " + daoClassName + " {"));
sb.append(StringUtils.formatSingleLine(1, "private DatabaseHelper helper;"));
sb.append(StringUtils.formatSingleLine(1, "private static volatile " + daoClassName + " instance = null;"));
sb.append("\n");
sb.append(StringUtils.formatSingleLine(1, "public static " + daoClassName + " getInstance(Context context) {"));
sb.append(StringUtils.formatSingleLine(2, "if (instance == null) {"));
sb.append(StringUtils.formatSingleLine(3, "synchronized (" + daoClassName + ".class) {"));
sb.append(StringUtils.formatSingleLine(4, "if (instance == null) {"));
sb.append(StringUtils.formatSingleLine(5, "instance = new " + daoClassName + "(context);"));
sb.append(StringUtils.formatSingleLine(4, "}"));
sb.append(StringUtils.formatSingleLine(3, "}"));
sb.append(StringUtils.formatSingleLine(2, "}"));
sb.append(StringUtils.formatSingleLine(2, "return instance;"));
sb.append(StringUtils.formatSingleLine(1, "}"));
sb.append("\n");
sb.append(StringUtils.formatSingleLine(1, "private " + daoClassName + "(Context context) {"));
sb.append(StringUtils.formatSingleLine(2, "helper = DatabaseHelper.getInstance(context);"));
sb.append(StringUtils.formatSingleLine(1, "}"));
sb.append("\n");
genDaoAddMethod(psiClass, map, sb); // add data
sb.append("\n");
genDaoGetListMethod(psiClass, map, sb); // get data list
sb.append("\n");
sb.append(StringUtils.formatSingleLine(0, "}"));
return sb.toString();
}
/**
* 添加数据
*/
public static void genDaoAddMethod(PsiClass psiClass, Map<String, String> map, StringBuilder sb) {
sb.append(StringUtils.formatSingleLine(1, "// 添加一条数据到数据库"));
sb.append(StringUtils.formatSingleLine(1, "public void add" + psiClass.getName() + "(" + psiClass.getName() + " data) {"));
sb.append(StringUtils.formatSingleLine(2, "SQLiteDatabase db = helper.getWritableDatabase();"));
sb.append("\n");
sb.append(StringUtils.formatSingleLine(2, "ContentValues value = new ContentValues();"));
for (String key : map.keySet()) {
String text = "value.put(\"" + key + "\"," + genDataGetStr("data", key, map.get(key)) + ");";
sb.append(StringUtils.formatSingleLine(2, text));
}
sb.append("\n");
sb.append(StringUtils.formatSingleLine(2, "db.insert(\"" + StringUtils.camelToUnderline(psiClass.getName()) + "\", null, value);"));
sb.append(StringUtils.formatSingleLine(1, "}"));
}
/**
* 获取数据
*/
public static void genDaoGetListMethod(PsiClass psiClass, Map<String, String> map, StringBuilder sb) {
sb.append(StringUtils.formatSingleLine(1, "// 返回数据库的所有数据"));
sb.append(StringUtils.formatSingleLine(1, "public List<" + psiClass.getName() + "> get" + psiClass.getName() + "List() {"));
sb.append(StringUtils.formatSingleLine(2, "SQLiteDatabase db = helper.getReadableDatabase();"));
sb.append(StringUtils.formatSingleLine(2, "List<" + psiClass.getName() + "> list = new ArrayList<>();"));
sb.append(StringUtils.formatSingleLine(2, "Cursor cursor = null;"));
sb.append(StringUtils.formatSingleLine(2, "try {"));
sb.append(StringUtils.formatSingleLine(3, "cursor = db.query(\"" + StringUtils.camelToUnderline(psiClass.getName()) + "\","));
sb.append(StringUtils.formatSingleLine(5, "null,"));
sb.append(StringUtils.formatSingleLine(5, "null,"));
sb.append(StringUtils.formatSingleLine(5, "null,"));
sb.append(StringUtils.formatSingleLine(5, "null,"));
sb.append(StringUtils.formatSingleLine(5, "null,"));
sb.append(StringUtils.formatSingleLine(5, "null);"));
sb.append(StringUtils.formatSingleLine(3, "if (cursor != null && cursor.moveToFirst()) {"));
sb.append(StringUtils.formatSingleLine(4, "do {"));
sb.append(StringUtils.formatSingleLine(5, "" + psiClass.getName() + " data = new " + psiClass.getName() + "();"));
for (String key : map.keySet()) {
sb.append(StringUtils.formatSingleLine(5, genSetDataStr("data", key, map.get(key))));
}
sb.append(StringUtils.formatSingleLine(5, "list.add(data);"));
sb.append(StringUtils.formatSingleLine(4, "} while (cursor.moveToNext());"));
sb.append(StringUtils.formatSingleLine(3, "}"));
sb.append(StringUtils.formatSingleLine(2, "} finally {"));
sb.append(StringUtils.formatSingleLine(3, "if (cursor != null) cursor.close();"));
sb.append(StringUtils.formatSingleLine(2, "}"));
sb.append(StringUtils.formatSingleLine(2, "return list;"));
sb.append(StringUtils.formatSingleLine(1, "}"));
}
/**
* 获取实体对象的成员变量值
* boolean值存储方式:true 存 “1” ,false 存 “0”。
*
* @param data 实体对象的变量名
* @param name 实体对象的成员变量名
* @return
*/
private static String genDataGetStr(String data, String name, String type) {
String getMethod;
if (name.startsWith("is")) {
getMethod = name + "()";
} else {
getMethod = "get" + StringUtils.firstToUpperCase(name) + "()";
}
String value = data + "." + getMethod;
if (type.equals(PsiType.BOOLEAN.getName())) {
return value + "?1:0";
}
return value;
}
/**
* 设置实体对象的成员变量值
*
* @param data 实体对象的变量名
* @param name 实体对象的成员变量名
* @return
*/
private static String genSetDataStr(String data, String name, String real_type) {
String type;
switch (real_type) {
case "int":
case "Integer":
type = "Int";
break;
case "long":
case "Long":
type = "Long";
break;
case "float":
case "Float":
type = "Float";
break;
case "double":
case "Double":
type = "Double";
break;
default:
type = "String";
break;
}
String text = data + ".%s(cursor.get%s(cursor.getColumnIndex(\"%s\")));";
String setMethod = "set" + StringUtils.firstToUpperCase(name);
if (name.startsWith("is")) {
setMethod = setMethod.replaceFirst("Is", "");
}
if (real_type.equals(PsiType.BOOLEAN.getName())) {
return String.format(data + ".%s(\"1\".equals(cursor.get%s(cursor.getColumnIndex(\"%s\"))));", setMethod, type, name);
} else {
return String.format(text, setMethod, type, name);
}
}
编写测试代码:
@Override
public void actionPerformed(AnActionEvent e) {
// 获取 Project
Project project = e.getData(PlatformDataKeys.PROJECT);
// 注意:生成代码与创建文件必须要在子线程中执行
WriteCommandAction.runWriteCommandAction(project, () -> {
// 获取db目录
VirtualFile dbDir = AndroidFileUtils.getDbFile(project);
// 获取当前正在编辑类的的所有成员遍历集合
Object[] objects = ParseEntryUtils.parseEntry(e);
if (objects == null || objects.length != 3) {
return;
}
PsiClass psiClass = (PsiClass) objects[0];
String entryPackageName = (String) objects[1];
Map<String, String> map = (Map<String, String>) objects[2];
if (psiClass == null || map == null || map.size() == 0) {
return;
}
DBGenerator.generatorDbHelperFile(project, psiClass, map,
null, dbDir);
DBGenerator.generatorDaoCodeFile(project, psiClass, entryPackageName, map, null, dbDir);
});
}
测试结果如下图所示:
3. 整合其他代码测试插件生成代码的正确性
直接用上一步生成的 DatabaseHelper.java 和 UserInfoDao.java 文件测试,在 MainActivity.java 编写测试代码如下所示:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UserInfoDao dao = UserInfoDao.getInstance(this);
// 测试添加一个数据
UserInfo userInfo = new UserInfo();
userInfo.setName("张三");
userInfo.setAge(18);
userInfo.setMan(true);
userInfo.setAddress("北京");
userInfo.setTel("10086");
// 添加数据库
dao.addUserInfo(userInfo);
userInfo = new UserInfo();
userInfo.setName("李四");
userInfo.setAge(28);
userInfo.setMan(false);
userInfo.setAddress("上海");
userInfo.setTel("10010");
// 添加数据库
dao.addUserInfo(userInfo);
print(dao);
}
// 输出数据库的数据
private void print(UserInfoDao dao) {
List<UserInfo> userInfoList = dao.getUserInfoList();
for (UserInfo info : userInfoList) {
System.out.println("info = " + info);
}
}
}
然后修改 DatabaseHelper.java 的数据库名称,如下图所示
运行测试结果如下:
说明插件生成的数据库代码是可以正常使用的。
五、为插件添加UI
上面代码已经可以完成插件的基本功能的开发了,为了更好用,还可以加入可操作的对话框。这次我们新增一个Dialog,可以勾选需要的数据类字段、主键。
新建对话框可以通过如下图所示的方法创建
输入需要创建的对话框的名称,如下图所示:
点击 “OK” ,会自动生成两个类,共同保存在 DBDialog 文件夹中,如下图所示:
XXXDialog.java 页面逻辑控制类
XXXDialog.form 对话框UI布局样式
有点类似于 Android 里的 Activity.java 和 layout.xml 文件,其实就是 Java GUI。
.form布局类是可视化编辑的,可以直接从右侧的控件库中拖到 UI 上,然后在左边的页面中选择对应控件修改属性。
编辑好页面后,就可以在类似于 Activity.java 类中处理逻辑了,但不同于 Activity 里需要 findViewById 去定位控件,而这里是直接用名字匹配的。
这里不做过多的解释,如果不懂的可以查阅官方文档获取查看 Java GUI 的具体用法。参考文档
https://docs.oracle.com/javase/tutorial/uiswing/layout/index.html
说明:在 DBDialog 设置监听,当点击确定按钮是返回用户选择的属性以及主键,接口如下:
public interface OnGenerateListener {
void onGenerate(ArrayList<PsiField> fields, PsiField priKeyField);
}
用法如下所示:
@Override
public void actionPerformed(AnActionEvent e) {
// 获取 Project
Project project = e.getData(PlatformDataKeys.PROJECT);
PsiFile file = e.getData(PlatformDataKeys.PSI_FILE);
PsiClass clazz = PluginUtils.getFileClass(file);
DBDialog dialog = new DBDialog(clazz);
dialog.setOnGenerateListener((fields, priKeyField) -> {
// 注意:生成代码与创建文件必须要在子线程中执行
WriteCommandAction.runWriteCommandAction(project, () -> {
// 获取db目录
VirtualFile dbDir = AndroidFileUtils.getDbFile(project);
// 获取当前正在编辑类的的所有成员遍历集合
Object[] objects = ParseEntryUtils.parseEntry(e);
if (objects == null || objects.length != 3) {
return;
}
PsiClass psiClass = (PsiClass) objects[0];
String entryPackageName = (String) objects[1];
// 没有界面时默认全选
// Map<String, String> map = (Map<String, String>) objects[2];
// 有界面时选择的属性
Map<String, String> map = parseFields(fields);
if (psiClass == null || map == null || map.size() == 0) {
return;
}
DBGenerator.generatorDbHelperFile(project, psiClass, map,
priKeyField, dbDir);
DBGenerator.generatorDaoCodeFile(project, psiClass, entryPackageName, map, priKeyField, dbDir);
});
});
dialog.pack();
dialog.setVisible(true);
}
private Map<String, String> parseFields(ArrayList<PsiField> fields) {
Map<String,String> map = new HashMap<>();
for (PsiField p : fields) {
// 成员变量的类型
String type = p.getType().getPresentableText();
// 成员变量的名称
String name = p.getName();
map.put(name, type);
}
return map;
}
运行结果如下图所示: