Intellij IDE 插件开发--DDLCreator

多说两句

开场白,很久都没有写博客了。。。 因为最近都在深入的去看elasticsearch,希望之后会有时间写一下关于elasticsearch的笔记。最近发现开发过程当中,我会不经意的去想写一些小东西提高整个开发效率,例如我写了给予AOP的Service方法级别的权限校验框架,给予AOP写的全局方法级别的参数校验框架等。当然,我也希望以后会有机会把这些框架贡献出来让大家参考和吐槽,但是这两天也写了一个比较有意思的东西,一款intellij IDE的插件。

由于我开发的大部分项目都使用了Liquibase,所以我需要经常去编写建表和添加字段的DDL语句。这个让我觉得相当麻烦,而且浪费时间,我已经定义好了JPA Entity了我就不想再做个翻译员把这些东西翻译成建表的DDL语句,特别是一些字段名还有描述,明明已经在entity里面定义写好注释了,还要ctl C + ctl V 拷贝到SQL的文件当中,进行持续化。哎 心累···

第一时间我是想马上百度去搜索获得类似这种的插件的,事实上找不到,我在百度和Google都尝试找了一下 也没有什么发现。所以我就想自己写一个吧。由于我也是刚刚开始去学习写这种插件,所以很多API可能用得不太6 可能有很多非常方便的API我也没有用上,有小伙伴看见我的代码里面有比较笨的地方,欢迎在评论里面写写你的方法,让我学习学习。边学边做的东西不出BUG我也就很开心了,没有什么要求。哈哈哈

一、插件使用展示

考虑到可能有部分人和我一样,一开始想找这个功能的插件去使用,没有打算去学习如何去开发插件的同学(本来如果找到有我也没有打算去写)。我这里就展示一下我的DDLCreator是如何使用的(插件安装请看后面)

Intellij IDE 插件开发--DDLCreator_第1张图片

请忽略辣鸡的表结构,事实上已经完成我初衷的想法,通过Entity对象直接生成SQL 的 DDL语句。目前已经支持大部分的JPA的annotation识别,但是是否完善我还需要在实际使用过程之中发现不足。但是基本是可用的,如果你需要这个功能,而且也不嫌弃的话可以通过以下链接下载并根据第二节内容进行安装。

下载地址:https://plugins.jetbrains.com/plugin/11271-ddlcreator

但是事实上我看到官方对我的JAR进行验证的时候,发现我使用了一些新的API导致旧版本intellij IDE不兼容的问题。这些我会后续进行修改,但是我测试的2018.3 intellij的版本是不存在问题的【测试了一下 2018.1以下都不兼容,包含2018.1 后续慢慢兼容,因为我有两三台电脑还是2017版本的,迫不得已想用就升级IDE吧】。官方的测试也是通过的,以下是关于插件的官方测试结果:

Intellij IDE 插件开发--DDLCreator_第2张图片

二、安装DDLCreator

下载好JAR之后,我们通过setting的plugin进行安装和其他的插件安装方式一样,不过需要通过本地jar的形式安装而已。

Intellij IDE 插件开发--DDLCreator_第3张图片

三、创建intellij plugin项目

先看个截图

Intellij IDE 插件开发--DDLCreator_第4张图片

Project SDK 就是intellij IDE的SDK 这个SDK就是相应的intellij IDE所提供的API,SDK版本越高API就越新,写android的同学应该很清楚了,我就这样入坑了选择了最高的SDK然后在2018.1版本以下的intellij 根本无法支持。所以说下次写还是搞个2016的版本比较靠谱

需要支持的framework自己要需要就选就可以了。

四、创建intellij plugin 项目结构:

Intellij IDE 插件开发--DDLCreator_第5张图片

其中resources目录下有一份plugin.xml 文件,这份XML文件主要配置关于这个插件的信息


  org.tonyyan.plugin.ddlcreator
  DDLCreator
  1.2
  TONYYAN

  

  
  

  
  

  
  

  
    
  

  
    
    
      
      
    
  

  • version:插件的版本号
  • vendor:供应商信息
  • description:插件信息描述
  • change-notes:版本修改记录
  • actions:这个就是一些插件的功能,每个功能用一个action,这里我写了一个action CreateTableDLL【我打错了本来想写DDL来着】这个是全局唯一的ID 是用来定位到你的action的。class属性标识action的实现类、description属性是action的描述、popup 是否需要弹出式菜单
  • add-to-group:这个功能添加到GenerateGroup 菜单里面,就是我们平时打开 getter setter的菜单  anchor属性代表功能的位置为最后

其实创建一个Action也可以通过GUI的形式去创建:

Intellij IDE 插件开发--DDLCreator_第6张图片

Intellij IDE 插件开发--DDLCreator_第7张图片

一毛一样的东西 GUI都可以实现,其实我也是用GUI来创建action的~

四、AnAction实现

首先先贴出最关键的两个类第一个是继承AnAction类的Suport类,主要实现主要的核心功能,如分析字段,字段信息对象转换等等。第二个要贴出的是Support类的子类,主要实现其Create DDL语句的功能。

public abstract class CreatorSupport extends AnAction {


    public abstract String createDDL(PsiClass psiClass);


    /**
     * 执行分析生成操作
     *
     * @param e
     */
    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here

        // 获取当前编辑的文件, 通过PsiFile可获得PsiClass, PsiField等对象
        PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE);
        PsiJavaFile javaFile = (PsiJavaFile) psiFile;
        PsiClass[] psiClasses = javaFile.getClasses();
        StringBuffer result = new StringBuffer();
        for (PsiClass pClass : psiClasses) {
            String ddl = createDDL(pClass);
            result.append(ddl);
        }
        openDialog(result.toString());
    }

    /**
     * 打开结果对话框
     *
     * @param result
     */
    protected void openDialog(String result) {
        Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
        int w = (int) (screensize.width * 0.3);
        int h = (int) (screensize.height * 0.3);

        ResultDialog dialog = new ResultDialog(result);
        dialog.setSize(w, h);
        dialog.pack();
        dialog.setLocation((int) (screensize.width * 0.5) - (int) (w * 0.5), (int) (screensize.height * 0.5) - (int) (h * 0.5));
        dialog.setVisible(true);

    }

    /**
     * 分析代码中的字段内容获得TableField对象
     *
     * @param psiField
     * @return
     */
    protected TableField getTableField(PsiField psiField) {
        if (psiField.hasModifierProperty(PsiModifier.FINAL) || psiField.hasModifierProperty(PsiModifier.STATIC)) {
            return null;
        }
        String typeStr = psiField.getType().getCanonicalText();
        TableField tableField = new TableField();
        tableField.setName(Convertor.Camel2Underline(psiField.getName()));
        if (psiField.getDocComment() != null) {
            tableField.setDesc(clearDesc(psiField.getDocComment().getText()));
        }
        tableField.setPrimayKey(false);
        tableField.setGeneratedValue(false);
        String getterName = Convertor.getFieldGetterName(psiField.getName());
        PsiMethod[] getterMethods = psiField.getContainingClass().findMethodsByName(getterName, true);
        if (getterMethods != null && getterMethods.length > 0) {
            for (PsiMethod targetMethod : getterMethods) {
                if (targetMethod.getParameterList() == null || targetMethod.getParameterList().isEmpty()) {
                    tableField = tableFieldPropertyFillIn(tableField, psiField, targetMethod.getAnnotations());
                    break;
                }
            }
            if (tableField == null) {
                return tableField;
            }
        }
        tableField = tableFieldPropertyFillIn(tableField, psiField, psiField.getAnnotations());
        if (tableField == null) {
            return tableField;
        }
        if (!tableField.isHasTypeTranslate()) {
            tableField.setType(TypeTranslator.sqlTypeTranslate(typeStr));
            tableField.setHasTypeTranslate(true);
        }
        return tableField;
    }


    private TableField tableFieldPropertyFillIn(TableField tableField, PsiField psiField, PsiAnnotation[] annotations) {
        for (PsiAnnotation annotation : annotations) {
            if (annotation.getQualifiedName().equals("javax.persistence.Transient")) {
                return null;
            }
            if (annotation.getQualifiedName().equals("javax.persistence.OneToMany")
                    || annotation.getQualifiedName().equals("javax.persistence.ManyToMany")) {
                return null;
            } else if (annotation.getQualifiedName().equals("javax.persistence.OneToOne")) {
                for (JvmAnnotationAttribute attr : annotation.getAttributes()) {
                    if (attr.getAttributeName().equals("mappedBy")) {
                        return null;
                    }
                }
            }
            if (annotation.getQualifiedName().equals("javax.persistence.JoinColumn")) {
                for (JvmAnnotationAttribute attr : annotation.getAttributes()) {
                    if (attr.getAttributeName().equals("name")) {
                        tableField.setName(getAttrTxtValue(attr));
                    } else if (attr.getAttributeName().equals("nullable")) {
                        if (getAttrTxtValue(attr).equals("false")) {
                            tableField.setNullable(false);
                        } else {
                            tableField.setNullable(true);
                        }
                    }
                }
                TableField mapperField = getTargetIdField(psiField);
                tableField.setType(mapperField.getType());
                tableField.setHasTypeTranslate(mapperField.isHasTypeTranslate());
                tableField.setLength(mapperField.getLength());
                return tableField;
            } else if (annotation.getQualifiedName().equals("javax.persistence.Column")) {
                for (JvmAnnotationAttribute attr : annotation.getAttributes()) {
                    if (attr.getAttributeName().equals("name")) {
                        tableField.setName(getAttrTxtValue(attr));
                    } else if (attr.getAttributeName().equals("length")) {
                        tableField.setLength(Integer.valueOf(getAttrTxtValue(attr)));
                    } else if (attr.getAttributeName().equals("nullable")) {
                        if (getAttrTxtValue(attr).equals("false")) {
                            tableField.setNullable(false);
                        } else {
                            tableField.setNullable(true);
                        }
                    }
                }
            }
            if (annotation.getQualifiedName().equals("javax.persistence.Id")) {
                tableField.setPrimayKey(true);
            }
            if (annotation.getQualifiedName().equals("javax.persistence.GeneratedValue")) {
                tableField.setGeneratedValue(true);
            }
        }
        return tableField;
    }


    /**
     * ManyToOne或者OneToOne
     *
     * @param psiField
     * @return
     */
    protected TableField getTargetIdField(PsiField psiField) {
        PsiClass psiClass = JavaPsiFacade.getInstance(psiField.getProject())
                .findClass(psiField.getType().getCanonicalText(), GlobalSearchScope.projectScope(psiField.getProject()));
        for (PsiField targetField : psiClass.getFields()) {
            for (PsiAnnotation annotation : targetField.getAnnotations()) {
                if (annotation.getQualifiedName().equals("javax.persistence.Id")) {
                    TableField tableField = this.getTableField(targetField);
                    return tableField;
                }
            }
        }
        PsiMethod[] psiMethodArray = psiClass.getMethods();
        for (PsiMethod method : psiMethodArray) {
            if (Convertor.isFieldGetter(method.getName())) {
                continue;
            }
            for (PsiAnnotation annotation : method.getAnnotations()) {
                if (annotation.getQualifiedName().equals("javax.persistence.Id")) {
                    String fieldName = Convertor.getGetterFieldName(method.getName());
                    PsiField targetField = psiClass.findFieldByName(fieldName, true);
                    TableField tableField = this.getTableField(targetField);
                    return tableField;
                }
            }
        }
        return null;
    }


    /**
     * 文档注释整理
     *
     * @param docStr
     * @return
     */
    protected String clearDesc(String docStr) {
        if (docStr != null && docStr.length() > 0) {
            docStr = docStr.replace("/**", "");
            docStr = docStr.replace("*/", "");
            docStr = docStr.replace("*", "");
            docStr = docStr.replace("\n", "");
            docStr = docStr.trim();
        }
        return docStr;
    }


    /**
     * 获得annotation参数
     *
     * @param attr
     * @return
     */
    protected String getAttrTxtValue(JvmAnnotationAttribute attr) {
        String txt = attr.getAttributeValue().getSourceElement().getText();
        if (txt.contains("\"")) {
            txt = txt.substring(1, txt.length() - 1);
        }
        if (txt != null && txt.length() > 0) {
            txt = txt.trim();
        }
        return txt;
    }


    /**
     * 获得表名称
     *
     * @param psiClass
     * @return
     */
    protected String getTableName(PsiClass psiClass) {
        String tableName = psiClass.getName();
        String firstLetter = tableName.substring(0, 1).toLowerCase();
        tableName = firstLetter + tableName.substring(1, tableName.length());
        tableName = Convertor.Camel2Underline(tableName);
        for (PsiAnnotation psiAnnotation : psiClass.getAnnotations()) {
            if (psiAnnotation.getQualifiedName().equals("javax.persistence.Table")) {
                for (JvmAnnotationAttribute attribute : psiAnnotation.getAttributes()) {
                    if (attribute.getAttributeName().equals("name")) {
                        tableName = getAttrTxtValue(attribute);
                    }
                }
            }
        }
        return tableName;
    }
}

看不懂没有关系我会一个个方法去拆解然后介绍其中的API。当然了,我也是边研究边写的也写得不太优美,而且SWing我也不是特别熟所以写得太差请吐槽,有好的建议我也会虚心学习的。

第二个贴出来的类:

public class CreateTableDDL extends CreatorSupport {

    /**
     * 分析获得建表语句
     *
     * @param psiClass
     * @return
     */
    @Override
    public String createDDL(PsiClass psiClass) {
        String tableName = getTableName(psiClass);
        PsiField[] psiFields = psiClass.getFields();
        List tableFields = new ArrayList<>();
        for (PsiField psiField : psiFields) {
            TableField tableField = getTableField(psiField);
            if (tableField != null) {
                tableFields.add(tableField);
            }
        }
        StringBuffer createTableDDL = new StringBuffer("DROP TABLE IF EXISTS `" + tableName + "`;\n");
        createTableDDL.append("CREATE TABLE `" + tableName + "` (\n");
        for (int i = 0; i < tableFields.size(); i++) {
            createTableDDL.append("    " + getSqlOfColumnPart(tableFields.get(i)));
            if (i < tableFields.size() - 1) {
                createTableDDL.append(",");
            }
            createTableDDL.append("\n");
        }
        createTableDDL.append(") ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4;");
        return createTableDDL.toString();
    }


    /**
     * 将tableField转换成部分的创建表字段语句
     *
     * @param tableField
     * @return
     */
    private String getSqlOfColumnPart(TableField tableField) {
        StringBuffer sql = new StringBuffer();
        sql.append("`" + tableField.getName() + "` " + tableField.getType());
        if (tableField.getLength() != null) {
            sql.append("(" + tableField.getLength() + ")");
        }
        if (tableField.isNullable()) {
            sql.append(" NULL");
        } else {
            sql.append(" NOT NULL");
        }
        if (tableField.isPrimayKey()) {
            sql.append(" PRIMARY KEY");
        }
        if (tableField.isGeneratedValue()) {
            sql.append(" AUTO_INCREMENT");
        }
        if (tableField.getDesc() != null && tableField.getDesc().length() > 0) {
            sql.append(" COMMENT '" + tableField.getDesc() + "'");
        }
        return sql.toString();
    }

    /**
     * 分析当前编辑类是否存在@Entity 如果不存在则不显示功能按钮
     *
     * @param e
     */
    @Override
    public void update(AnActionEvent e) {
        super.update(e);
        // 获取当前编辑的文件, 通过PsiFile可获得PsiClass, PsiField等对象
        PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE);

        // 获取当前的project对象
        Project project = e.getProject();

        // 获取数据上下文
        DataContext dataContext = e.getDataContext();

        Editor editor = e.getData(PlatformDataKeys.EDITOR);

        if (editor == null) {
            e.getPresentation().setEnabled(false);
            return;
        }

        if (!(psiFile instanceof PsiJavaFile)) {
            e.getPresentation().setEnabled(false);
            return;
        }

        PsiJavaFile javaFile = (PsiJavaFile) psiFile;
        PsiClass[] psiClasses = javaFile.getClasses();
        boolean hasEntityAnnotation = false;
        for (PsiClass pClass : psiClasses) {
            for (PsiAnnotation psiAnnotation : pClass.getAnnotations()) {
                if (psiAnnotation.getQualifiedName().equals("javax.persistence.Entity")) {
                    hasEntityAnnotation = true;
                }
            }
        }
        if (!hasEntityAnnotation) {
            e.getPresentation().setEnabled(false);
        }
    }
}

五、功能实现分析

1、首先是我们可以发现AnAction类需要我们实现如下的方法:

//当点击到功能的菜单按钮的时候执行
public void actionPerformed(AnActionEvent e)

//环境更新的时候执行 主要判断按钮是否需要显示或者是否可用
public void update(AnActionEvent e)

2、update动态显示按钮

/**
     * 分析当前编辑类是否存在@Entity 如果不存在则不显示功能按钮
     *
     * @param e
     */
    @Override
    public void update(AnActionEvent e) {
        super.update(e);
        // 获取当前编辑的文件, 通过PsiFile可获得PsiClass, PsiField等对象
        PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE);

        // 获取当前的project对象
        Project project = e.getProject();

        // 获取数据上下文
        DataContext dataContext = e.getDataContext();

        Editor editor = e.getData(PlatformDataKeys.EDITOR);

        //如果没有打开编辑框就不显示
        if (editor == null) {
            e.getPresentation().setEnabled(false);
            return;
        }
        
        //如果当前打开的文件不是JAVA文件也不用显示
        if (!(psiFile instanceof PsiJavaFile)) {
            e.getPresentation().setEnabled(false);
            return;
        }

        //是否存在@JPA Entity标签 如果没有不显示按钮
        PsiJavaFile javaFile = (PsiJavaFile) psiFile;
        PsiClass[] psiClasses = javaFile.getClasses();
        boolean hasEntityAnnotation = false;
        for (PsiClass pClass : psiClasses) {
            for (PsiAnnotation psiAnnotation : pClass.getAnnotations()) {
                if (psiAnnotation.getQualifiedName().equals("javax.persistence.Entity")) {
                    hasEntityAnnotation = true;
                }
            }
        }
        if (!hasEntityAnnotation) {
            e.getPresentation().setEnabled(false);
        }
    }

3、actionPerformed点击按钮的业务

    /**
     * 执行分析生成操作
     *
     * @param e
     */
    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here

        // 获取当前编辑的文件, 通过PsiFile可获得PsiClass, PsiField等对象
        PsiFile psiFile = e.getData(LangDataKeys.PSI_FILE);
        PsiJavaFile javaFile = (PsiJavaFile) psiFile;
        //获得当前编辑的类,如果定义了多个类就直接返回多个DDL语句 当然几乎不可能
        PsiClass[] psiClasses = javaFile.getClasses();
        StringBuffer result = new StringBuffer();
        for (PsiClass pClass : psiClasses) {
            //分析并获得DDL语句
            String ddl = createDDL(pClass);
            result.append(ddl);
        }
        //打开Dialog 显示DDL语句
        openDialog(result.toString());
    }

4、根据实体类组织SQL

我们分析的重点是当前类的属性信息还其JPA的annotation,这些信息都是我们平时在创建Entity用到的配置,按照这种套路我们就可以获得到整个表的字段结构,然后我们需要去遍历字段,然后获得字段名称、字段类型、@Column中的信息、@JoinColum信息、@Table信息、@Id等。我们先看整体的组织结构,后面下一个section会讲解如何分析字段

/**
     * 分析获得建表语句
     *
     * @param psiClass
     * @return
     */
    @Override
    public String createDDL(PsiClass psiClass) {

        //获得当前类的名称
        String tableName = getTableName(psiClass);
        //获得当前类的字段
        PsiField[] psiFields = psiClass.getFields();

        //遍历分析获得字段内容,包括getter和其字段的 annotation标签
        List tableFields = new ArrayList<>();
        for (PsiField psiField : psiFields) {
            //解析
            TableField tableField = getTableField(psiField);
            if (tableField != null) {
                tableFields.add(tableField);
            }
        }

        //组织SQL
        StringBuffer createTableDDL = new StringBuffer("DROP TABLE IF EXISTS `" + tableName + "`;\n");
        createTableDDL.append("CREATE TABLE `" + tableName + "` (\n");
        for (int i = 0; i < tableFields.size(); i++) {
            //获得字段的列SQL
            createTableDDL.append("    " + getSqlOfColumnPart(tableFields.get(i)));
            if (i < tableFields.size() - 1) {
                createTableDDL.append(",");
            }
            createTableDDL.append("\n");
        }
        createTableDDL.append(") ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4;");
        return createTableDDL.toString();
    }

注意如果字段分析方法返回一个空,就证明这个字段会忽略掉,目前我会忽略掉@ManyToMany 还有@Transient、@OneToMany 除了@ManyToMany 其他都是正常情况下应该忽略的,但是ManyToMany的情况比较复杂我就没有处理了(主要是懒)

5、类字段分析并转换成TableField对象

正如刚刚所说的我们需要花大量的时间去研究分析字段和它的Getter方法,获得其字段和JPA的annotation信息去做进一步的事情

上代码!最恶心的字段分析,本来想重新封装的 由于懒还有想搞其他的

我将写上详情到不能再详细的注释

/**
     * 分析代码中的字段内容获得TableField对象
     *
     * @param psiField
     * @return
     */
    protected TableField getTableField(PsiField psiField) {
        //如果是常量或者是静态变量直接忽略
        if (psiField.hasModifierProperty(PsiModifier.FINAL) || psiField.hasModifierProperty(PsiModifier.STATIC)) {
            return null;
        }
        //获得字段的类型名称
        String typeStr = psiField.getType().getCanonicalText();
        //这个类主要是封装 SQL字段的length、name、type、desc等等
        TableField tableField = new TableField();
        //获得字段名并转换成下划线形式
        tableField.setName(Convertor.Camel2Underline(psiField.getName()));
        
        //获得是否有文档注释如果有直接用到SQL字段声明的 COMMENT里面去
        if (psiField.getDocComment() != null) {
            //调用clearDesc方法主要是自己定义了一个文档整理的方法去除空格换行 星号等等
            tableField.setDesc(clearDesc(psiField.getDocComment().getText()));
        }
        //默认认为不是主键
        tableField.setPrimayKey(false);
        tableField.setGeneratedValue(false);
        
        //通过自己定义的一个Convertor.getFieldGetterName 将字段名 转换获得其Getter方法
        String getterName = Convertor.getFieldGetterName(psiField.getName());
        
        //获得Getter方法名称后 通过名称获得方法对象数组  最后有个Boolean参数 我看API的源代码 原来是是否在父类里面找,不能写写注释吗真的是
        PsiMethod[] getterMethods = psiField.getContainingClass().findMethodsByName(getterName, true);
        
        
        //如果有Getter方法 先去Getter方法 里面去获得annotation 信息并注入到tableField对象里面去
        if (getterMethods != null && getterMethods.length > 0) {
            for (PsiMethod targetMethod : getterMethods) {
                if (targetMethod.getParameterList() == null || targetMethod.getParameterList().isEmpty()) {
                    //这个是分析JPA annotation的 下面代码里有 详细解释
                    tableField = tableFieldPropertyFillIn(tableField, psiField, targetMethod.getAnnotations());
                    break;
                }
            }
            //如果返回空 证明这个字段需要忽略直接返回空忽略
            if (tableField == null) {
                return tableField;
            }
        }
        
        //然后通过属性去获得JPA annotation信息 这个将会覆盖方法的JPA annotation信息 所以 如果出现两个一样的JPA annotation内容 会以field的为主
        tableField = tableFieldPropertyFillIn(tableField, psiField, psiField.getAnnotations());
        
        //该忽略就忽略
        if (tableField == null) {
            return tableField;
        }
        
        //这里需要注意的是 如果是ManyToOne的话 需要去对应的映射当中获得ID字段 然后分析获得其SQL字段类型 所以我们会在这里做一次转换转换成SQL 字段类型后下次就不需要转了
        if (!tableField.isHasTypeTranslate()) {
            
            //这里是自己写的一个类型转换器,将JAVA类型 转换成 SQL的字段类型,如果是无法解释将会返回一个unknown 并不会异常 让业务可以继续执行下去
            tableField.setType(TypeTranslator.sqlTypeTranslate(typeStr));
            //标识已经转换成SQL 类型了
            tableField.setHasTypeTranslate(true);
        }
        //返回字段对象
        return tableField;
    }


    //主要获得Getter方法或者是字段的JPA annotation信息
    private TableField tableFieldPropertyFillIn(TableField tableField, PsiField psiField, PsiAnnotation[] annotations) {

        //一开始先忽略字段
        for (PsiAnnotation annotation : annotations) {

            //忽略Transient的
            if (annotation.getQualifiedName().equals("javax.persistence.Transient")) {
                return null;
            }
            //忽略OneToMany 一般都是在ManyToOne定义字段
            if (annotation.getQualifiedName().equals("javax.persistence.OneToMany")
                    || annotation.getQualifiedName().equals("javax.persistence.ManyToMany")) {
                return null;
            } else if (annotation.getQualifiedName().equals("javax.persistence.OneToOne")) {
                //如果是OneToOne的话,分析是不是mappedBy对方对象的(这个描述很变扭)
                for (JvmAnnotationAttribute attr : annotation.getAttributes()) {
                    if (attr.getAttributeName().equals("mappedBy")) {
                        return null;
                    }
                }
            }

            //获得JoinColumn annotation 然后提取是否为空 提取字段名称等等
            if (annotation.getQualifiedName().equals("javax.persistence.JoinColumn")) {
                for (JvmAnnotationAttribute attr : annotation.getAttributes()) {
                    if (attr.getAttributeName().equals("name")) {
                        tableField.setName(getAttrTxtValue(attr));
                    } else if (attr.getAttributeName().equals("nullable")) {
                        if (getAttrTxtValue(attr).equals("false")) {
                            tableField.setNullable(false);
                        } else {
                            tableField.setNullable(true);
                        }
                    }
                }
                //提取MapperField 主要是映射方的ID字段信息 然后并将映射对象的类型和类型长度设置到这个外检字段里面去
                TableField mapperField = getTargetIdField(psiField);
                tableField.setType(mapperField.getType());
                tableField.setHasTypeTranslate(mapperField.isHasTypeTranslate());
                tableField.setLength(mapperField.getLength());
                return tableField;
            } else if (annotation.getQualifiedName().equals("javax.persistence.Column")) {
                //分析普通字段 获得字段名
                for (JvmAnnotationAttribute attr : annotation.getAttributes()) {
                    if (attr.getAttributeName().equals("name")) {
                        tableField.setName(getAttrTxtValue(attr));
                        //获得字段长度
                    } else if (attr.getAttributeName().equals("length")) {
                        tableField.setLength(Integer.valueOf(getAttrTxtValue(attr)));
                        //是否为空
                    } else if (attr.getAttributeName().equals("nullable")) {
                        if (getAttrTxtValue(attr).equals("false")) {
                            tableField.setNullable(false);
                        } else {
                            tableField.setNullable(true);
                        }
                    }
                }
            }
            //获得是否为主键
            if (annotation.getQualifiedName().equals("javax.persistence.Id")) {
                tableField.setPrimayKey(true);
            }
            //获得是否自动生成主键
            if (annotation.getQualifiedName().equals("javax.persistence.GeneratedValue")) {
                tableField.setGeneratedValue(true);
            }
        }
        return tableField;
    }

/**
     * ManyToOne或者OneToOne 就去获得映射的类ID字段信息
     *
     * @param psiField
     * @return
     */
    protected TableField getTargetIdField(PsiField psiField) {
        
        //获得映射类 
        PsiClass psiClass = JavaPsiFacade.getInstance(psiField.getProject())
                .findClass(psiField.getType().getCanonicalText(), GlobalSearchScope.projectScope(psiField.getProject()));
        
        //寻找ID字段的路上
        for (PsiField targetField : psiClass.getFields()) {
            for (PsiAnnotation annotation : targetField.getAnnotations()) {
                if (annotation.getQualifiedName().equals("javax.persistence.Id")) {
                    TableField tableField = this.getTableField(targetField);
                    return tableField;
                }
            }
        }
        
        //字段找不到就找getter方法
        PsiMethod[] psiMethodArray = psiClass.getMethods();
        for (PsiMethod method : psiMethodArray) {
            if (Convertor.isFieldGetter(method.getName())) {
                continue;
            }
            for (PsiAnnotation annotation : method.getAnnotations()) {
                if (annotation.getQualifiedName().equals("javax.persistence.Id")) {
                    String fieldName = Convertor.getGetterFieldName(method.getName());
                    PsiField targetField = psiClass.findFieldByName(fieldName, true);
                    TableField tableField = this.getTableField(targetField);
                    return tableField;
                }
            }
        }
        //都找不到就没有办法了
        return null;
    }

需要注意的是我们用了很多Psi开头的类,这些类就是intellij IDE 所定义的文档信息类,例如PsiAnnotation PsiFile PsiField等等 所以看里面的方法名称基本可以判断是用来干什么的,不过有些方法只有新版的intellij SDK才支持,所以在旧版的intellij IDE使用这些用了新API方法的插件就会异常,都是那些NoSuchMethod等等的异常,一看就知道的。

6、顺便写一下Swing的Dialog

万事具备,就差一个展示结果的东西了。这里我们需要创建一个Dialog显示结果。我这里还提供一个Copy按钮直接Copy 生成的DDL语句。

/**
     * 打开结果对话框
     *
     * @param result
     */
    protected void openDialog(String result) {
        Dimension screensize = Toolkit.getDefaultToolkit().getScreenSize();
        int w = (int) (screensize.width * 0.3);
        int h = (int) (screensize.height * 0.3);

        ResultDialog dialog = new ResultDialog(result);
        dialog.setSize(w, h);
        dialog.pack();
        dialog.setLocation((int) (screensize.width * 0.5) - (int) (w * 0.5), (int) (screensize.height * 0.5) - (int) (h * 0.5));
        dialog.setVisible(true);

    }

然后呢~  看看我的Dialog代码,虽然我是懵的。

public class ResultDialog extends JDialog {
    private JPanel contentPane;
    private JButton buttonCopy;
    private JButton buttonCancel;
    private JTextArea textAreaResult;

    public ResultDialog(String result) {
        setTitle("TONY CREATE TABLE DDL GENERATOR");
        setContentPane(contentPane);
        setModal(true);
        getRootPane().setDefaultButton(buttonCopy);

        textAreaResult.setText(result);


        buttonCopy.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onOK();
            }
        });

        buttonCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        });

        // call onCancel() when cross is clicked
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                onCancel();
            }
        });

        // call onCancel() on ESCAPE
        contentPane.registerKeyboardAction(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                onCancel();
            }
        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
    }

    private void onOK() {
        // add your code here
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        StringSelection selection = new StringSelection(this.textAreaResult.getText());
        clipboard.setContents(selection, null);
        dispose();
    }

    private void onCancel() {
        // add your code here if necessary
        dispose();
    }

    public static void main(String[] args) {
        ResultDialog dialog = new ResultDialog("");
        dialog.pack();
        dialog.setVisible(true);
        System.exit(0);
    }
}

六、打包上传到plugin repository

看截图

Intellij IDE 插件开发--DDLCreator_第8张图片

然后直接有个JAR包了,然后可以上传到intellij IDE plugin repository里面,我们不是为了可以得到他官方推荐。重点是可以帮我保存就可以了。而且重点可以帮我测试一下有什么环境下不兼容,虽然我不会去改,反正我能用···· 开玩笑的有时间还是会改的。

intellij IDE 官方的 plugin repository 插件中心 地址在这:https://plugins.jetbrains.com/

如果有兴趣我可以上传到GITHUB,有兴趣获得源代码的可以在评论留言。

 

你可能感兴趣的:(JAVA,SpringBoot,JPA,intellij)