Android Studio Plugin —— 自动生成数据库代码的插件

前言

本篇实战制作一个自动生成 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;
    }

运行结果如下图所示:

源码下载地址:https://gitee.com/lx0713/db-plugin

扫描下方二维码关注公众号,获取更多技术干货。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值