/*
 * Copyright (c) 2018.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.itfsw.mybatis.generator.plugins;

import com.itfsw.mybatis.generator.plugins.utils.*;
import com.itfsw.mybatis.generator.plugins.utils.hook.ILogicalDeletePluginHook;
import com.itfsw.mybatis.generator.plugins.utils.hook.IModelBuilderPluginHook;
import com.itfsw.mybatis.generator.plugins.utils.hook.IOptimisticLockerPluginHook;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.*;
import org.mybatis.generator.codegen.mybatis3.ListUtilities;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;
import org.mybatis.generator.internal.util.JavaBeansUtil;
import org.mybatis.generator.internal.util.StringUtility;

import java.util.*;

/**
 * ---------------------------------------------------------------------------
 * 乐观锁插件
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2018/4/26 10:24
 * ---------------------------------------------------------------------------
 */
public class OptimisticLockerPlugin extends BasePlugin implements IModelBuilderPluginHook, ILogicalDeletePluginHook {
    public static final String METHOD_DELETE_WITH_VERSION_BY_EXAMPLE = "deleteWithVersionByExample";  // 方法名
    public static final String METHOD_DELETE_WITH_VERSION_BY_PRIMARY_KEY = "deleteWithVersionByPrimaryKey";  // 方法名
    public static final String METHOD_LOGICAL_DELETE_WITH_VERSION_BY_EXAMPLE = "logicalDeleteWithVersionByExample";  // 方法名
    public static final String METHOD_LOGICAL_DELETE_WITH_VERSION_BY_PRIMARY_KEY = "logicalDeleteWithVersionByPrimaryKey";  // 方法名
    public static final String METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_SELECTIVE = "updateWithVersionByExampleSelective";  // 方法名
    public static final String METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_WITH_BLOBS = "updateWithVersionByExampleWithBLOBs";  // 方法名
    public static final String METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_WITHOUT_BLOBS = "updateWithVersionByExample";  // 方法名
    public static final String METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_SELECTIVE = "updateWithVersionByPrimaryKeySelective";  // 方法名
    public static final String METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_WITH_BLOBS = "updateWithVersionByPrimaryKeyWithBLOBs";  // 方法名
    public static final String METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_WITHOUT_BLOBS = "updateWithVersionByPrimaryKey";  // 方法名
    public static final String METHOD_NEXT_VERSION = "nextVersion";  // 方法名
    public static final String SQL_UPDATE_BY_EXAMPLE_WITH_VERSION_WHERE_CLAUSE = "Update_By_Example_With_Version_Where_Clause";

    public static final String PRO_VERSION_COLUMN = "versionColumn";  // 版本列-Key
    public static final String PRO_CUSTOMIZED_NEXT_VERSION = "customizedNextVersion";    // 使用用户自定义nextVersion key

    private Map<IntrospectedTable, List<XmlElement>> sqlMaps = new HashMap<>(); // sqlMap xml 节点
    private IntrospectedColumn versionColumn;   // 版本列
    private boolean customizedNextVersion;  // 使用用户自定义nextVersion

    @Override
    public void initialized(IntrospectedTable introspectedTable) {
        super.initialized(introspectedTable);
        sqlMaps.put(introspectedTable, new ArrayList<>());

        // 读取并验证版本列
        String versionColumn = introspectedTable.getTableConfigurationProperty(PRO_VERSION_COLUMN);
        if (versionColumn != null) {
            this.versionColumn = introspectedTable.getColumn(versionColumn);
            if (this.versionColumn == null) {
                warnings.add("itfsw(乐观锁插件):表" + introspectedTable.getFullyQualifiedTable() + "配置的版本列(" + introspectedTable.getTableConfigurationProperty(PRO_VERSION_COLUMN) + ")没有找到！");
            }
        } else {
            this.versionColumn = null;
        }

        // 自定义nextVersion
        // 首先获取全局配置
        Properties properties = getProperties();
        String customizedNextVersion = properties.getProperty(PRO_CUSTOMIZED_NEXT_VERSION);
        // 获取表单独配置，如果有则覆盖全局配置
        if (introspectedTable.getTableConfigurationProperty(PRO_CUSTOMIZED_NEXT_VERSION) != null) {
            customizedNextVersion = introspectedTable.getTableConfigurationProperty(PRO_CUSTOMIZED_NEXT_VERSION);
        }
        if (StringUtility.stringHasValue(customizedNextVersion) && StringUtility.isTrue(customizedNextVersion)) {
            this.customizedNextVersion = true;
        } else {
            this.customizedNextVersion = false;
        }

        super.initialized(introspectedTable);
    }

    // ========================================= method 生成 ============================================
    @Override
    public boolean clientDeleteByExampleMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            FormatTools.addMethodWithBestPosition(
                    interfaze,
                    this.replaceDeleteExampleMethod(introspectedTable, method, interfaze, METHOD_DELETE_WITH_VERSION_BY_EXAMPLE)
            );
        }
        return super.clientDeleteByExampleMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientDeleteByPrimaryKeyMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            FormatTools.addMethodWithBestPosition(
                    interfaze,
                    this.replaceDeletePrimaryKeyMethod(introspectedTable, method, interfaze, METHOD_DELETE_WITH_VERSION_BY_PRIMARY_KEY)
            );
        }
        return super.clientDeleteByPrimaryKeyMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientLogicalDeleteByExampleMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            if (this.customizedNextVersion) {
                Method newMethod = JavaElementGeneratorTools.generateMethod(
                        METHOD_LOGICAL_DELETE_WITH_VERSION_BY_EXAMPLE,
                        JavaVisibility.DEFAULT,
                        FullyQualifiedJavaType.getIntInstance(),
                        new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "version", "@Param(\"version\")"),
                        new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "nextVersion", "@Param(\"nextVersion\")"),
                        new Parameter(new FullyQualifiedJavaType(introspectedTable.getExampleType()), "example", "@Param(\"example\")")
                );
                commentGenerator.addGeneralMethodComment(newMethod, introspectedTable);
                FormatTools.addMethodWithBestPosition(interfaze, newMethod);
            } else {
                FormatTools.addMethodWithBestPosition(
                        interfaze,
                        this.replaceDeleteExampleMethod(introspectedTable, method, interfaze, METHOD_LOGICAL_DELETE_WITH_VERSION_BY_EXAMPLE)
                );
            }
        }
        return true;
    }

    @Override
    public boolean clientLogicalDeleteByPrimaryKeyMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            if (this.customizedNextVersion) {
                Method newMethod = JavaElementGeneratorTools.generateMethod(
                        METHOD_LOGICAL_DELETE_WITH_VERSION_BY_PRIMARY_KEY,
                        JavaVisibility.DEFAULT,
                        FullyQualifiedJavaType.getIntInstance(),
                        new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "version", "@Param(\"version\")"),
                        new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "nextVersion", "@Param(\"nextVersion\")"),
                        new Parameter(method.getParameters().get(0).getType(), method.getParameters().get(0).getName(), "@Param(\"key\")")
                );
                commentGenerator.addGeneralMethodComment(newMethod, introspectedTable);
                FormatTools.addMethodWithBestPosition(interfaze, newMethod);
            } else {
                FormatTools.addMethodWithBestPosition(
                        interfaze,
                        this.replaceDeletePrimaryKeyMethod(introspectedTable, method, interfaze, METHOD_LOGICAL_DELETE_WITH_VERSION_BY_PRIMARY_KEY)
                );
            }
        }
        return true;
    }

    @Override
    public boolean clientUpdateByExampleSelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            Method withVersion = this.replaceUpdateExampleMethod(introspectedTable, method, interfaze, METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_SELECTIVE);
            if (PluginTools.getHook(IOptimisticLockerPluginHook.class).clientUpdateWithVersionByExampleSelectiveMethodGenerated(withVersion, interfaze, introspectedTable)) {
                FormatTools.addMethodWithBestPosition(interfaze, withVersion);
            }
        }
        return super.clientUpdateByExampleSelectiveMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientUpdateByExampleWithBLOBsMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            FormatTools.addMethodWithBestPosition(
                    interfaze,
                    this.replaceUpdateExampleMethod(introspectedTable, method, interfaze, METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_WITH_BLOBS)
            );
        }
        return super.clientUpdateByExampleWithBLOBsMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientUpdateByExampleWithoutBLOBsMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            FormatTools.addMethodWithBestPosition(
                    interfaze,
                    this.replaceUpdateExampleMethod(introspectedTable, method, interfaze, METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_WITHOUT_BLOBS)
            );
        }
        return super.clientUpdateByExampleWithoutBLOBsMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientUpdateByPrimaryKeySelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            Method withVersion = this.replaceUpdatePrimaryKeyMethod(introspectedTable, method, interfaze, METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_SELECTIVE);
            if (PluginTools.getHook(IOptimisticLockerPluginHook.class).clientUpdateWithVersionByPrimaryKeySelectiveMethodGenerated(withVersion, interfaze, introspectedTable)) {
                FormatTools.addMethodWithBestPosition(interfaze, withVersion);
            }
        }
        return super.clientUpdateByPrimaryKeySelectiveMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientUpdateByPrimaryKeyWithBLOBsMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            FormatTools.addMethodWithBestPosition(
                    interfaze,
                    this.replaceUpdatePrimaryKeyMethod(introspectedTable, method, interfaze, METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_WITH_BLOBS)
            );
        }
        return super.clientUpdateByPrimaryKeyWithBLOBsMethodGenerated(method, interfaze, introspectedTable);
    }

    @Override
    public boolean clientUpdateByPrimaryKeyWithoutBLOBsMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            FormatTools.addMethodWithBestPosition(
                    interfaze,
                    this.replaceUpdatePrimaryKeyMethod(introspectedTable, method, interfaze, METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_WITHOUT_BLOBS)
            );
        }
        return super.clientUpdateByPrimaryKeyWithoutBLOBsMethodGenerated(method, interfaze, introspectedTable);
    }

    // ========================================= model 生成 ============================================

    @Override
    public boolean modelSetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        if (this.versionColumn != null && this.customizedNextVersion && introspectedColumn.getActualColumnName().equals(this.versionColumn.getActualColumnName())) {
            // nextVersion 方法
            Method nextVersion = JavaElementGeneratorTools.generateMethod(
                    METHOD_NEXT_VERSION,
                    JavaVisibility.PUBLIC,
                    null,
                    new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "version")
            );
            commentGenerator.addSetterComment(nextVersion, introspectedTable, this.versionColumn);
            JavaElementGeneratorTools.generateMethodBody(
                    nextVersion,
                    "this." + this.versionColumn.getJavaProperty() + " = version;"
            );

            FormatTools.addMethodWithBestPosition(topLevelClass, nextVersion);
        }

        return super.modelSetterMethodGenerated(method, topLevelClass, introspectedColumn, introspectedTable, modelClassType);
    }

    /**
     * Model builder set 方法生成
     * @param method
     * @param topLevelClass
     * @param builderClass
     * @param introspectedColumn
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelBuilderSetterMethodGenerated(Method method, TopLevelClass topLevelClass, InnerClass builderClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null && this.customizedNextVersion && introspectedColumn.getActualColumnName().equals(this.versionColumn.getActualColumnName())) {
            // nextVersion 方法
            Method nextVersion = JavaElementGeneratorTools.generateMethod(
                    METHOD_NEXT_VERSION,
                    JavaVisibility.PUBLIC,
                    builderClass.getType(),
                    new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "version")
            );
            commentGenerator.addSetterComment(nextVersion, introspectedTable, this.versionColumn);

            Method setterMethod = JavaBeansUtil.getJavaBeansSetter(this.versionColumn, context, introspectedTable);

            JavaElementGeneratorTools.generateMethodBody(
                    nextVersion,
                    "obj." + setterMethod.getName() + "(version);",
                    "return this;"
            );

            FormatTools.addMethodWithBestPosition(builderClass, nextVersion);
        }

        return true;
    }

    /**
     * Model builder class 生成
     * @param topLevelClass
     * @param builderClass
     * @param columns
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelBuilderClassGenerated(TopLevelClass topLevelClass, InnerClass builderClass, List<IntrospectedColumn> columns, IntrospectedTable introspectedTable) {
        return true;
    }

    // ========================================= sqlMap 生成 ============================================

    @Override
    public boolean sqlMapDeleteByExampleElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            this.sqlMaps.get(introspectedTable).add(
                    this.replaceExampleXmlElement(introspectedTable, element, METHOD_DELETE_WITH_VERSION_BY_EXAMPLE)
            );
        }
        return super.sqlMapDeleteByExampleElementGenerated(element, introspectedTable);
    }

    /**
     * @param element
     * @param introspectedTable
     * @return
     * @see org.mybatis.generator.codegen.mybatis3.xmlmapper.elements.DeleteByPrimaryKeyElementGenerator#addElements(XmlElement)
     */
    @Override
    public boolean sqlMapDeleteByPrimaryKeyElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            this.sqlMaps.get(introspectedTable).add(
                    this.replacePrimaryKeyXmlElement(introspectedTable, element, METHOD_DELETE_WITH_VERSION_BY_PRIMARY_KEY, false)
            );
        }
        return super.sqlMapDeleteByPrimaryKeyElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapUpdateByExampleSelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            this.sqlMaps.get(introspectedTable).add(
                    this.generateSqlMapUpdate(
                            introspectedTable,
                            ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getAllColumns()),
                            METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_SELECTIVE,
                            true,
                            true
                    )
            );
        }
        return super.sqlMapUpdateByExampleSelectiveElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapUpdateByExampleWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            this.sqlMaps.get(introspectedTable).add(
                    this.generateSqlMapUpdate(
                            introspectedTable,
                            ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getAllColumns()),
                            METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_WITH_BLOBS,
                            false,
                            true
                    )
            );
        }
        return super.sqlMapUpdateByExampleWithBLOBsElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapUpdateByExampleWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            this.sqlMaps.get(introspectedTable).add(
                    this.generateSqlMapUpdate(
                            introspectedTable,
                            ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getNonBLOBColumns()),
                            METHOD_UPDATE_WITH_VERSION_BY_EXAMPLE_WITHOUT_BLOBS,
                            false,
                            true
                    )
            );
        }
        return super.sqlMapUpdateByExampleWithoutBLOBsElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapUpdateByPrimaryKeySelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            this.sqlMaps.get(introspectedTable).add(
                    this.generateSqlMapUpdate(
                            introspectedTable,
                            ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getNonPrimaryKeyColumns()),
                            METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_SELECTIVE,
                            true,
                            false
                    )
            );
        }
        return super.sqlMapUpdateByPrimaryKeySelectiveElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapUpdateByPrimaryKeyWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            this.sqlMaps.get(introspectedTable).add(
                    this.generateSqlMapUpdate(
                            introspectedTable,
                            ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getNonPrimaryKeyColumns()),
                            METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_WITH_BLOBS,
                            false,
                            false
                    )
            );
        }
        return super.sqlMapUpdateByPrimaryKeyWithBLOBsElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapUpdateByPrimaryKeyWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            this.sqlMaps.get(introspectedTable).add(
                    this.generateSqlMapUpdate(
                            introspectedTable,
                            ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getBaseColumns()),
                            METHOD_UPDATE_WITH_VERSION_BY_PRIMARY_KEY_WITHOUT_BLOBS,
                            false,
                            false
                    )
            );
        }
        return super.sqlMapUpdateByPrimaryKeyWithoutBLOBsElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            for (XmlElement element : sqlMaps.get(introspectedTable)) {
                FormatTools.addElementWithBestPosition(document.getRootElement(), element);
            }
        }
        return super.sqlMapDocumentGenerated(document, introspectedTable);
    }

    @Override
    public boolean sqlMapExampleWhereClauseElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            if (XmlElementTools.getAttribute(element, "id").getValue().equals("Update_By_Example_Where_Clause")) {
                XmlElement withVersionEle = new XmlElement("sql");
                withVersionEle.addAttribute(new Attribute("id", SQL_UPDATE_BY_EXAMPLE_WITH_VERSION_WHERE_CLAUSE));
                commentGenerator.addComment(withVersionEle);

                // 版本语句
                XmlElement whereEle = new XmlElement("where ");
                withVersionEle.addElement(whereEle);

                whereEle.addElement(new TextElement(this.generateVersionEleStr()));

                // 附加原生语句
                XmlElement ifEle = new XmlElement("if");
                whereEle.addElement(ifEle);

                ifEle.addAttribute(new Attribute("test", "example.oredCriteria.size() > 0"));

                // 原生的foreach
                XmlElement foreachEle = (XmlElement) XmlElementTools.findXmlElements(element, "where").get(0).getElements().get(0);
                ifEle.addElement(foreachEle);

                foreachEle.addAttribute(new Attribute("open", "and ("));
                foreachEle.addAttribute(new Attribute("close", ")"));

                sqlMaps.get(introspectedTable).add(withVersionEle);

                context.getPlugins().sqlMapExampleWhereClauseElementGenerated(withVersionEle, introspectedTable);
            }
        }
        return super.sqlMapExampleWhereClauseElementGenerated(element, introspectedTable);
    }

    @Override
    public boolean sqlMapLogicalDeleteByExampleElementGenerated(Document document, XmlElement element, IntrospectedColumn logicalDeleteColumn, String logicalDeleteValue, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            FormatTools.addElementWithBestPosition(document.getRootElement(), this.generateSqlMapLogicalDelete(
                    introspectedTable,
                    METHOD_LOGICAL_DELETE_WITH_VERSION_BY_EXAMPLE,
                    logicalDeleteColumn,
                    logicalDeleteValue,
                    true
            ));
        }
        return true;
    }

    @Override
    public boolean sqlMapLogicalDeleteByPrimaryKeyElementGenerated(Document document, XmlElement element, IntrospectedColumn logicalDeleteColumn, String logicalDeleteValue, IntrospectedTable introspectedTable) {
        if (this.versionColumn != null) {
            FormatTools.addElementWithBestPosition(document.getRootElement(), this.generateSqlMapLogicalDelete(
                    introspectedTable,
                    METHOD_LOGICAL_DELETE_WITH_VERSION_BY_PRIMARY_KEY,
                    logicalDeleteColumn,
                    logicalDeleteValue,
                    false
            ));
        }
        return true;
    }

    @Override
    public boolean logicalDeleteEnumGenerated(IntrospectedColumn logicalDeleteColumn) {
        return false;
    }

    // =================================================== 一些生成方法 ========================================

    /**
     * 生成版本判断节点
     * @return
     */
    private String generateVersionEleStr() {
        StringBuilder sb = new StringBuilder();
        sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(this.versionColumn));
        sb.append(" = ");
        sb.append("#{version,jdbcType=");
        sb.append(this.versionColumn.getJdbcTypeName());
        if (StringUtility.stringHasValue(this.versionColumn.getTypeHandler())) {
            sb.append(",typeHandler=");
            sb.append(this.versionColumn.getTypeHandler());
        }
        sb.append("}");
        return sb.toString();
    }

    /**
     * 替换Example 方法
     * @param introspectedTable
     * @param method
     * @param interfaze
     * @param methodName
     * @return
     */
    private Method replaceUpdateExampleMethod(IntrospectedTable introspectedTable, Method method, Interface interfaze, String methodName) {
        Method withVersionMethod = new Method(method);

        // 替换方法名
        withVersionMethod.setName(methodName);
        FormatTools.replaceGeneralMethodComment(commentGenerator, withVersionMethod, introspectedTable);

        Parameter versionParam = new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "version", "@Param(\"version\")");

        withVersionMethod.addParameter(0, versionParam);

        return withVersionMethod;
    }

    /**
     * 替换Example 方法
     * @param introspectedTable
     * @param method
     * @param interfaze
     * @param methodName
     * @return
     */
    private Method replaceDeleteExampleMethod(IntrospectedTable introspectedTable, Method method, Interface interfaze, String methodName) {
        Method withVersionMethod = new Method(method);

        // 替换方法名
        withVersionMethod.setName(methodName);
        FormatTools.replaceGeneralMethodComment(commentGenerator, withVersionMethod, introspectedTable);

        Parameter versionParam = new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "version", "@Param(\"version\")");
        Parameter exampleParam = new Parameter(new FullyQualifiedJavaType(introspectedTable.getExampleType()), "example", "@Param(\"example\")");

        withVersionMethod.getParameters().clear();
        withVersionMethod.addParameter(versionParam);
        withVersionMethod.addParameter(exampleParam);

        return withVersionMethod;
    }

    /**
     * 替换主键 方法
     * @param introspectedTable
     * @param method
     * @param interfaze
     * @param methodName
     * @return
     */
    private Method replaceUpdatePrimaryKeyMethod(IntrospectedTable introspectedTable, Method method, Interface interfaze, String methodName) {
        Method withVersionMethod = new Method(method);

        // 替换方法名
        withVersionMethod.setName(methodName);
        FormatTools.replaceGeneralMethodComment(commentGenerator, withVersionMethod, introspectedTable);

        Parameter versionParam = new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "version", "@Param(\"version\")");
        Parameter recordParam = new Parameter(method.getParameters().get(0).getType(), "record", "@Param(\"record\")");

        withVersionMethod.getParameters().clear();
        withVersionMethod.addParameter(versionParam);
        withVersionMethod.addParameter(recordParam);

        return withVersionMethod;
    }

    /**
     * 替换主键 方法
     * @param introspectedTable
     * @param method
     * @param interfaze
     * @param methodName
     * @return
     */
    private Method replaceDeletePrimaryKeyMethod(IntrospectedTable introspectedTable, Method method, Interface interfaze, String methodName) {
        Method withVersionMethod = new Method(method);

        // 替换方法名
        withVersionMethod.setName(methodName);
        FormatTools.replaceGeneralMethodComment(commentGenerator, withVersionMethod, introspectedTable);

        Parameter versionParam = new Parameter(this.versionColumn.getFullyQualifiedJavaType(), "version", "@Param(\"version\")");
        Parameter keyParam = new Parameter(method.getParameters().get(0).getType(), method.getParameters().get(0).getName(), "@Param(\"key\")");

        withVersionMethod.getParameters().clear();
        withVersionMethod.addParameter(versionParam);
        withVersionMethod.addParameter(keyParam);

        return withVersionMethod;
    }

    /**
     * 替换Example
     * @param introspectedTable
     * @param element
     * @param id
     * @return
     */
    private XmlElement replaceExampleXmlElement(IntrospectedTable introspectedTable, XmlElement element, String id) {
        XmlElement withVersionEle = XmlElementTools.clone(element);

        XmlElementTools.replaceAttribute(withVersionEle, new Attribute("id", id));
        XmlElementTools.replaceAttribute(withVersionEle, new Attribute("parameterType", "map"));

        FormatTools.replaceComment(commentGenerator, withVersionEle);

        // 替换查询语句
        List<XmlElement> ifEles = XmlElementTools.findXmlElements(withVersionEle, "if");
        for (XmlElement ifEle : ifEles) {
            List<XmlElement> includeEles = XmlElementTools.findXmlElements(ifEle, "include");
            for (XmlElement includeEle : includeEles) {
                if (XmlElementTools.getAttribute(includeEle, "refid") != null) {
                    XmlElementTools.replaceAttribute(includeEle, new Attribute("refid", SQL_UPDATE_BY_EXAMPLE_WITH_VERSION_WHERE_CLAUSE));
                }
            }
        }

        return withVersionEle;
    }

    /**
     * 替换 主键查询
     * @param introspectedTable
     * @param element
     * @param id
     * @param update
     * @return
     */
    private XmlElement replacePrimaryKeyXmlElement(IntrospectedTable introspectedTable, XmlElement element, String id, boolean update) {
        XmlElement withVersionEle = XmlElementTools.clone(element);

        XmlElementTools.replaceAttribute(withVersionEle, new Attribute("id", id));
        XmlElementTools.replaceAttribute(withVersionEle, new Attribute("parameterType", "map"));

        FormatTools.replaceComment(commentGenerator, withVersionEle);

        // 替换查询语句
        Iterator<Element> elementIterator = withVersionEle.getElements().iterator();
        boolean flag = false;
        while (elementIterator.hasNext()) {
            Element ele = elementIterator.next();
            if (ele instanceof TextElement && ((TextElement) ele).getContent().matches(".*where.*")) {
                flag = true;
            }

            if (flag) {
                elementIterator.remove();
            }
        }

        // where 语句
        withVersionEle.addElement(new TextElement("where " + this.generateVersionEleStr()));
        if (introspectedTable.getPrimaryKeyColumns().size() == 1) {
            IntrospectedColumn introspectedColumn = introspectedTable.getPrimaryKeyColumns().get(0);
            StringBuilder sb = new StringBuilder();
            sb.append("  and ");
            sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn));
            sb.append(" = ");
            if (update) {
                sb.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, "record."));
            } else {
                sb.append("#{key,jdbcType=");
                sb.append(introspectedColumn.getJdbcTypeName());
                if (StringUtility.stringHasValue(introspectedColumn.getTypeHandler())) {
                    sb.append(",typeHandler=");
                    sb.append(introspectedColumn.getTypeHandler());
                }
                sb.append("}");
            }
            withVersionEle.addElement(new TextElement(sb.toString()));
        } else {
            for (IntrospectedColumn introspectedColumn : introspectedTable.getPrimaryKeyColumns()) {
                StringBuilder sb = new StringBuilder();
                sb.append("  and ");
                sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn));
                sb.append(" = ");
                sb.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, update ? "record." : "key."));
                withVersionEle.addElement(new TextElement(sb.toString()));
            }
        }

        return withVersionEle;
    }

    /**
     * 生成update sql map
     * @param introspectedTable
     * @param columns
     * @param id
     * @param selective
     * @param byExample
     * @return
     */
    private XmlElement generateSqlMapUpdate(IntrospectedTable introspectedTable, List<IntrospectedColumn> columns, String id, boolean selective, boolean byExample) {
        // 移除版本列
        Iterator<IntrospectedColumn> columnIterator = columns.iterator();
        while (columnIterator.hasNext()) {
            IntrospectedColumn introspectedColumn = columnIterator.next();
            if (introspectedColumn.getActualColumnName().equals(this.versionColumn.getActualColumnName())) {
                columnIterator.remove();
            }
        }

        XmlElement updateEle = new XmlElement("update");

        updateEle.addAttribute(new Attribute("id", id));
        updateEle.addAttribute(new Attribute("parameterType", "map"));
        commentGenerator.addComment(updateEle);

        updateEle.addElement(new TextElement("update " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()));
        if (selective) {
            XmlElement setEle = new XmlElement("set");
            updateEle.addElement(setEle);

            // set 节点
            XmlElement setsEle = XmlElementGeneratorTools.generateSetsSelective(columns, "record.");
            setEle.addElement(setsEle);

            XmlElement needVersionEle;
            if (PluginTools.getHook(IOptimisticLockerPluginHook.class).generateSetsSelectiveElement(columns, this.versionColumn, setsEle)) {
                needVersionEle = setEle;
            } else {
                needVersionEle = setsEle;
            }

            // 版本自增
            needVersionEle.addElement(0, this.generateVersionSetEle(selective));
        } else {
            // 版本自增
            updateEle.addElement(this.generateVersionSetEle(selective));
            // set 节点
            List<Element> setsEles = XmlElementGeneratorTools.generateSets(columns, "record.");
            //  XmlElementGeneratorTools.generateSets, 因为传入参数不可能带IdentityAndGeneratedAlwaysColumn所以返回的是set列表而不可能是trim 元素
            for (Element ele : setsEles) {
                updateEle.addElement(ele);
            }
        }

        // 更新条件
        if (byExample) {
            XmlElement ifElement = new XmlElement("if");
            ifElement.addAttribute(new Attribute("test", "_parameter != null"));

            XmlElement includeElement = new XmlElement("include");
            includeElement.addAttribute(new Attribute("refid", SQL_UPDATE_BY_EXAMPLE_WITH_VERSION_WHERE_CLAUSE));
            ifElement.addElement(includeElement);

            updateEle.addElement(ifElement);
        } else {
            updateEle = this.replacePrimaryKeyXmlElement(introspectedTable, updateEle, id, true);
        }

        return updateEle;
    }

    /**
     * 生成LogicalDelete sql map
     * @param introspectedTable
     * @param id
     * @param logicalDeleteColumn
     * @param logicalDeleteValue
     * @param byExample
     * @return
     */
    private XmlElement generateSqlMapLogicalDelete(IntrospectedTable introspectedTable, String id, IntrospectedColumn logicalDeleteColumn, String logicalDeleteValue, boolean byExample) {
        XmlElement updateEle = new XmlElement("update");

        updateEle.addAttribute(new Attribute("id", id));
        updateEle.addAttribute(new Attribute("parameterType", "map"));
        commentGenerator.addComment(updateEle);

        updateEle.addElement(new TextElement("update " + introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime()));

        StringBuilder sb = new StringBuilder("set ");
        // 版本自增
        if (this.customizedNextVersion) {
            sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(this.versionColumn));
            sb.append(" = ");
            sb.append("#{nextVersion,jdbcType=");
            sb.append(this.versionColumn.getJdbcTypeName());
            if (StringUtility.stringHasValue(this.versionColumn.getTypeHandler())) {
                sb.append(",typeHandler=");
                sb.append(this.versionColumn.getTypeHandler());
            }
            sb.append("}");
        } else {
            sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(this.versionColumn));
            sb.append(" = ");
            sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(this.versionColumn));
            sb.append(" + 1");
        }
        sb.append(",");

        // 逻辑删除 set
        sb.append(logicalDeleteColumn.getActualColumnName());
        sb.append(" = ");
        sb.append(XmlElementGeneratorTools.generateLogicalDeleteColumnValue(logicalDeleteColumn, logicalDeleteValue));
        updateEle.addElement(new TextElement(sb.toString()));

        // 更新条件
        if (byExample) {
            XmlElement ifElement = new XmlElement("if");
            ifElement.addAttribute(new Attribute("test", "_parameter != null"));

            XmlElement includeElement = new XmlElement("include");
            includeElement.addAttribute(new Attribute("refid", SQL_UPDATE_BY_EXAMPLE_WITH_VERSION_WHERE_CLAUSE));
            ifElement.addElement(includeElement);

            updateEle.addElement(ifElement);
        } else {
            updateEle = this.replacePrimaryKeyXmlElement(introspectedTable, updateEle, id, false);
        }

        return updateEle;
    }

    /**
     * 生成版本号set节点
     * @param selective
     * @return
     */
    private TextElement generateVersionSetEle(boolean selective) {
        if (this.customizedNextVersion) {
            return new TextElement(
                    (selective ? "" : "set ")
                            + MyBatis3FormattingUtilities.getEscapedColumnName(this.versionColumn)
                            + " = "
                            + MyBatis3FormattingUtilities.getParameterClause(this.versionColumn, "record.")
                            + ","
            );
        } else {
            return new TextElement(
                    (selective ? "" : "set ")
                            + MyBatis3FormattingUtilities.getEscapedColumnName(this.versionColumn)
                            + " = "
                            + MyBatis3FormattingUtilities.getEscapedColumnName(this.versionColumn)
                            + " + 1,"
            );
        }
    }
}


/*
 * Copyright (c) 2017.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.itfsw.mybatis.generator.plugins;

import com.itfsw.mybatis.generator.plugins.utils.*;
import com.itfsw.mybatis.generator.plugins.utils.enhanced.SpecTypeArgumentsFullyQualifiedJavaType;
import com.itfsw.mybatis.generator.plugins.utils.hook.IIncrementsPluginHook;
import com.itfsw.mybatis.generator.plugins.utils.hook.ILombokPluginHook;
import com.itfsw.mybatis.generator.plugins.utils.hook.IModelBuilderPluginHook;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Element;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;
import org.mybatis.generator.internal.util.JavaBeansUtil;
import org.mybatis.generator.internal.util.StringUtility;

import java.util.ArrayList;
import java.util.List;

/**
 * ---------------------------------------------------------------------------
 * 增量插件
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/6/19 15:20
 * ---------------------------------------------------------------------------
 */
public class IncrementsPlugin extends BasePlugin implements IModelBuilderPluginHook, IIncrementsPluginHook, ILombokPluginHook {
    public static final String PRO_INCREMENTS_COLUMNS = "incrementsColumns";  // incrementsColumns property
    public static final String FIELD_INC_MAP = "incrementsColumnsInfoMap";    // 为了防止和用户数据库字段冲突，特殊命名
    public static final String METHOD_GET_INC_MAP = "incrementsColumnsInfoMap"; // 获取inc map
    public static final String METHOD_INC_CHECK = "hasIncsForColumn";   // inc 检查方法名称

    private List<IntrospectedColumn> incColumns;   // 表启用增量操作的字段
    private InnerEnum incEnum;  // 增量Enum
    private InnerClass incEnumBuilder;  // 添加了增量Enum的builder

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param warnings
     * @return
     */
    @Override
    public boolean validate(List<String> warnings) {

        // 插件使用前提是使用了ModelBuilderPlugin插件
        if (!(PluginTools.checkDependencyPlugin(getContext(), ModelBuilderPlugin.class) || PluginTools.checkDependencyPlugin(getContext(), LombokPlugin.class))) {
            warnings.add("itfsw:插件" + this.getClass().getTypeName() + "插件需配合" + ModelBuilderPlugin.class.getTypeName() + "或者" + LombokPlugin.class.getTypeName() + "插件使用！");
            return false;
        }

        return super.validate(warnings);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param introspectedTable
     */
    @Override
    public void initialized(IntrospectedTable introspectedTable) {
        super.initialized(introspectedTable);
        this.incColumns = new ArrayList<>();
        this.incEnum = null;
        this.incEnumBuilder = null;

        String incrementsColumns = introspectedTable.getTableConfigurationProperty(IncrementsPlugin.PRO_INCREMENTS_COLUMNS);
        if (StringUtility.stringHasValue(incrementsColumns)) {
            // 切分
            String[] incrementsColumnsStrs = incrementsColumns.split(",");
            for (String incrementsColumnsStr : incrementsColumnsStrs) {
                IntrospectedColumn column = IntrospectedTableTools.safeGetColumn(introspectedTable, incrementsColumnsStr);
                if (column == null) {
                    warnings.add("itfsw:插件" + IncrementsPlugin.class.getTypeName() + "插件没有找到column为" + incrementsColumnsStr.trim() + "的字段！");
                } else {
                    incColumns.add(column);
                }
            }
        }
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpdateByExampleSelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        generatedWithSelective(element, introspectedTable, true);
        return super.sqlMapUpdateByExampleSelectiveElementGenerated(element, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpdateByExampleWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        generatedWithoutSelective(element, introspectedTable, true);
        return super.sqlMapUpdateByExampleWithBLOBsElementGenerated(element, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpdateByExampleWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        generatedWithoutSelective(element, introspectedTable, true);
        return super.sqlMapUpdateByExampleWithoutBLOBsElementGenerated(element, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpdateByPrimaryKeySelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        generatedWithSelective(element, introspectedTable, false);
        return super.sqlMapUpdateByPrimaryKeySelectiveElementGenerated(element, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpdateByPrimaryKeyWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        generatedWithoutSelective(element, introspectedTable, false);
        return super.sqlMapUpdateByPrimaryKeyWithBLOBsElementGenerated(element, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpdateByPrimaryKeyWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        generatedWithoutSelective(element, introspectedTable, false);
        return super.sqlMapUpdateByPrimaryKeyWithoutBLOBsElementGenerated(element, introspectedTable);
    }

    // =============================================== ILombokPluginHook ===================================================

    @Override
    public boolean modelBaseRecordBuilderClassGenerated(TopLevelClass topLevelClass, List<IntrospectedColumn> columns, IntrospectedTable introspectedTable) {
        return this.lombokBuilderClassGenerated(topLevelClass, columns, introspectedTable);
    }

    @Override
    public boolean modelPrimaryKeyBuilderClassGenerated(TopLevelClass topLevelClass, List<IntrospectedColumn> columns, IntrospectedTable introspectedTable) {
        return this.lombokBuilderClassGenerated(topLevelClass, columns, introspectedTable);
    }

    @Override
    public boolean modelRecordWithBLOBsBuilderClassGenerated(TopLevelClass topLevelClass, List<IntrospectedColumn> columns, IntrospectedTable introspectedTable) {
        return this.lombokBuilderClassGenerated(topLevelClass, columns, introspectedTable);
    }

    /**
     * Lombok Builder 生成
     * @param topLevelClass
     * @param columns
     * @param introspectedTable
     * @return
     */
    private boolean lombokBuilderClassGenerated(TopLevelClass topLevelClass, List<IntrospectedColumn> columns, IntrospectedTable introspectedTable) {
        if (this.support()) {
            boolean find = false;
            for (IntrospectedColumn column : columns) {
                if (this.supportColumn(column)) {
                    find = true;
                    break;
                }
            }
            if (find) {
                // ----------------------------------- topLevelClass 方法 --------------------------------
                FullyQualifiedJavaType builderType = new FullyQualifiedJavaType(topLevelClass.getType().getShortName() + "." + topLevelClass.getType().getShortName() + "Builder");
                builderType.addTypeArgument(new SpecTypeArgumentsFullyQualifiedJavaType("<?, ?>"));

                // 增加构造函数
                Method constructor = new Method(topLevelClass.getType().getShortName());
                commentGenerator.addGeneralMethodComment(constructor, introspectedTable);
                constructor.setVisibility(JavaVisibility.PROTECTED);
                constructor.setConstructor(true);
                constructor.addParameter(new Parameter(builderType, "builder"));
                // 是否调用父类构造函数
                if (topLevelClass.getSuperClass() != null) {
                    constructor.addBodyLine("super(builder);");
                }
                for (IntrospectedColumn column : columns) {
                    Field field = JavaBeansUtil.getJavaBeansField(column, context, introspectedTable);
                    constructor.addBodyLine("this." + field.getName() + " = builder." + field.getName() + ";");
                }
                FormatTools.addMethodWithBestPosition(topLevelClass, constructor);

                // 增加静态builder方法实现和lombok一样
                Method builderMethod = JavaElementGeneratorTools.generateMethod(
                        "builder",
                        JavaVisibility.PUBLIC,
                        builderType
                );
                commentGenerator.addGeneralMethodComment(builderMethod, introspectedTable);
                builderMethod.setStatic(true);
                builderMethod.addBodyLine("return new " + topLevelClass.getType().getShortName() + "." + topLevelClass.getType().getShortName() + "BuilderImpl();");
                FormatTools.addMethodWithBestPosition(topLevelClass, builderMethod);

                // ------------------------------ builder Class ----------------------------------
                InnerClass builderCls = new InnerClass(topLevelClass.getType().getShortName() + "Builder");
                commentGenerator.addClassComment(builderCls, introspectedTable);
                builderCls.setVisibility(JavaVisibility.PUBLIC);
                builderCls.setStatic(true);
                builderCls.setAbstract(true);

                builderCls.getType().addTypeArgument(
                        new SpecTypeArgumentsFullyQualifiedJavaType("<C extends " + topLevelClass.getType().getShortName()
                                + ", B extends " + topLevelClass.getType().getShortName()
                                + "." + topLevelClass.getType().getShortName() + "Builder<C, B>>")
                );

                if (topLevelClass.getSuperClass() != null) {
                    FullyQualifiedJavaType superBuilderCls = new FullyQualifiedJavaType(topLevelClass.getSuperClass().getShortName() + "Builder");
                    superBuilderCls.addTypeArgument(new SpecTypeArgumentsFullyQualifiedJavaType("<C, B>"));
                    builderCls.setSuperClass(superBuilderCls);
                }

                // 类注解
                topLevelClass.addImportedType(LombokPlugin.EnumLombokAnnotations.SETTER.getClazz());
                builderCls.addAnnotation(LombokPlugin.EnumLombokAnnotations.SETTER.getAnnotation());
                topLevelClass.addImportedType(LombokPlugin.EnumLombokAnnotations.ACCESSORS_FLUENT_TRUE.getClazz());
                builderCls.addAnnotation(LombokPlugin.EnumLombokAnnotations.ACCESSORS_FLUENT_TRUE.getAnnotation());
                if (topLevelClass.getSuperClass() != null) {
                    topLevelClass.addImportedType(LombokPlugin.EnumLombokAnnotations.TO_STRING_CALL_SUPER.getClazz());
                    builderCls.addAnnotation(LombokPlugin.EnumLombokAnnotations.TO_STRING_CALL_SUPER.getAnnotation());
                } else {
                    topLevelClass.addImportedType(LombokPlugin.EnumLombokAnnotations.TO_STRING.getClazz());
                    builderCls.addAnnotation(LombokPlugin.EnumLombokAnnotations.TO_STRING.getAnnotation());
                }

                for (IntrospectedColumn introspectedColumn : columns) {
                    Field field = JavaBeansUtil.getJavaBeansField(introspectedColumn, context, introspectedTable);
                    field.getJavaDocLines().clear();
                    commentGenerator.addFieldComment(field, introspectedTable);

                    builderCls.addField(field);
                }

                // self 方法
                Method selfMethod1 = JavaElementGeneratorTools.generateMethod(
                        "self",
                        JavaVisibility.PROTECTED,
                        new FullyQualifiedJavaType("B")
                );
                commentGenerator.addGeneralMethodComment(selfMethod1, introspectedTable);
                FormatTools.addMethodWithBestPosition(builderCls, selfMethod1);

                // build 方法
                Method buildMethod1 = JavaElementGeneratorTools.generateMethod(
                        "build",
                        JavaVisibility.PUBLIC,
                        new FullyQualifiedJavaType("C")
                );
                commentGenerator.addGeneralMethodComment(buildMethod1, introspectedTable);
                FormatTools.addMethodWithBestPosition(builderCls, buildMethod1);

                topLevelClass.addInnerClass(builderCls);

                // --------------------------------- 生成自增插件需要的方法 --------------------------------------
                // 增加枚举
                if (this.incEnum == null) {
                    this.incEnum = this.generatedIncEnum(introspectedTable);
                    this.incEnumBuilder = builderCls;

                    builderCls.addInnerEnum(this.incEnum);
                    // topLevel class 添加必要的操作方法
                    this.addIncMethodToTopLevelClass(topLevelClass, builderCls, introspectedTable, true);

                    // Builder 中 添加字段支持
                    Field fIncrements = JavaElementGeneratorTools.generateField(
                            IncrementsPlugin.FIELD_INC_MAP,
                            JavaVisibility.PROTECTED,
                            new FullyQualifiedJavaType("Map<String, " + this.getIncEnum(builderCls, introspectedTable).getFullyQualifiedName() + ">"),
                            "new HashMap<String, " + this.getIncEnum(builderCls, introspectedTable).getFullyQualifiedName() + ">()"
                    );
                    commentGenerator.addFieldComment(fIncrements, introspectedTable);
                    builderCls.addField(fIncrements);

                    // Builder 构造函数增加 自增Map
                    constructor.addBodyLine("this." + IncrementsPlugin.FIELD_INC_MAP + ".putAll(builder." + IncrementsPlugin.FIELD_INC_MAP + ");");
                }

                FullyQualifiedJavaType builderType2 = new FullyQualifiedJavaType(topLevelClass.getType().getShortName() + "." + topLevelClass.getType().getShortName() + "Builder");
                builderType2.addTypeArgument(new SpecTypeArgumentsFullyQualifiedJavaType("<C, B>"));
                for (IntrospectedColumn column : columns) {
                    if (this.supportColumn(column)) {
                        Field field = JavaBeansUtil.getJavaBeansField(column, context, introspectedTable);
                        // 增加方法
                        Method mIncrements = JavaElementGeneratorTools.generateMethod(
                                field.getName(),
                                JavaVisibility.PUBLIC,
                                builderType2,
                                new Parameter(field.getType(), field.getName()),
                                new Parameter(this.getIncEnum(builderCls, introspectedTable), "inc")
                        );
                        commentGenerator.addSetterComment(mIncrements, introspectedTable, column);
                        mIncrements.addBodyLine("this." + IncrementsPlugin.FIELD_INC_MAP + ".put(\"" + column.getActualColumnName() + "\", inc);");
                        mIncrements.addBodyLine("return this." + field.getName() + "(" + field.getName() + ");");

                        FormatTools.addMethodWithBestPosition(builderCls, mIncrements);
                    }
                }

                // ------------------------------ builderImpl Class ----------------------------------
                InnerClass builderImplCls = new InnerClass(topLevelClass.getType().getShortName() + "BuilderImpl");
                commentGenerator.addClassComment(builderImplCls, introspectedTable);
                FullyQualifiedJavaType builderType1 = new FullyQualifiedJavaType(topLevelClass.getType().getShortName() + "." + topLevelClass.getType().getShortName() + "Builder");
                builderType1.addTypeArgument(new SpecTypeArgumentsFullyQualifiedJavaType(
                        "<" + topLevelClass.getType().getShortName() + ", " + topLevelClass.getType().getShortName() + "." + topLevelClass.getType().getShortName() + "BuilderImpl" + ">"
                ));
                builderImplCls.setSuperClass(builderType1);
                builderImplCls.setVisibility(JavaVisibility.PRIVATE);
                builderImplCls.setFinal(true);
                builderImplCls.setStatic(true);

                topLevelClass.addInnerClass(builderImplCls);

                // self 方法
                Method selfMethod = JavaElementGeneratorTools.generateMethod(
                        "self",
                        JavaVisibility.PROTECTED,
                        new FullyQualifiedJavaType(topLevelClass.getType().getShortName() + "." + topLevelClass.getType().getShortName() + "BuilderImpl")
                );
                commentGenerator.addGeneralMethodComment(selfMethod, introspectedTable);
                selfMethod.addBodyLine("return this;");
                FormatTools.addMethodWithBestPosition(builderImplCls, selfMethod);

                // build 方法
                Method buildMethod = JavaElementGeneratorTools.generateMethod(
                        "build",
                        JavaVisibility.PUBLIC,
                        topLevelClass.getType()
                );
                commentGenerator.addGeneralMethodComment(buildMethod, introspectedTable);
                buildMethod.addBodyLine("return new " + topLevelClass.getType().getShortName() + "(this);");
                FormatTools.addMethodWithBestPosition(builderImplCls, buildMethod);

                return false;
            }
        }
        return true;
    }

    // =============================================== IModelBuilderPluginHook ===================================================

    /**
     * Model builder class 生成
     * @param topLevelClass
     * @param builderClass
     * @param columns
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelBuilderClassGenerated(TopLevelClass topLevelClass, InnerClass builderClass, List<IntrospectedColumn> columns, IntrospectedTable introspectedTable) {
        if (this.support()) {
            if (this.incEnum == null) {
                this.incEnum = this.generatedIncEnum(introspectedTable);
                this.incEnumBuilder = builderClass;
                // 增加枚举
                builderClass.addInnerEnum(this.incEnum);
                // topLevel class 添加必要的操作方法
                this.addIncMethodToTopLevelClass(topLevelClass, builderClass, introspectedTable, false);
            }

            // Builder 中 添加字段支持
            for (IntrospectedColumn column : columns) {
                if (this.supportColumn(column)) {
                    Field field = JavaBeansUtil.getJavaBeansField(column, context, introspectedTable);
                    // 增加方法
                    Method mIncrements = JavaElementGeneratorTools.generateMethod(
                            field.getName(),
                            JavaVisibility.PUBLIC,
                            builderClass.getType(),
                            new Parameter(field.getType(), field.getName()),
                            new Parameter(this.getIncEnum(builderClass, introspectedTable), "inc")
                    );
                    commentGenerator.addSetterComment(mIncrements, introspectedTable, column);

                    Method setterMethod = JavaBeansUtil.getJavaBeansSetter(column, context, introspectedTable);
                    mIncrements.addBodyLine("obj." + IncrementsPlugin.FIELD_INC_MAP + ".put(\"" + column.getActualColumnName() + "\", inc);");
                    mIncrements.addBodyLine("obj." + setterMethod.getName() + "(" + field.getName() + ");");
                    mIncrements.addBodyLine("return this;");

                    FormatTools.addMethodWithBestPosition(builderClass, mIncrements);
                }
            }
        }
        return true;
    }

    /**
     * Model builder set 方法生成
     * @param method
     * @param topLevelClass
     * @param builderClass
     * @param introspectedColumn
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelBuilderSetterMethodGenerated(Method method, TopLevelClass topLevelClass, InnerClass builderClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable) {
        return true;
    }

    // =============================================== IIncrementsPluginHook ===================================================

    /**
     * 生成增量操作节点
     * @param introspectedColumn
     * @param prefix
     * @param hasComma
     * @return
     */
    @Override
    public List<Element> incrementSetElementGenerated(IntrospectedColumn introspectedColumn, String prefix, boolean hasComma) {
        List<Element> list = new ArrayList<>();

        if (this.supportColumn(introspectedColumn)) {
            // 1. column = 节点
            list.add(new TextElement(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn) + " = "));

            // 2. 选择节点
            // 条件
            XmlElement choose = new XmlElement("choose");

            // 没有启用增量操作
            XmlElement when = new XmlElement("when");
            when.addAttribute(new Attribute(
                    "test",
                    (prefix != null ? prefix : "_parameter.") + IncrementsPlugin.METHOD_INC_CHECK
                            + "('" + MyBatis3FormattingUtilities.escapeStringForMyBatis3(introspectedColumn.getActualColumnName()) + "')"
            ));
            TextElement spec = new TextElement(
                    MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn)
                            + " ${" + (prefix != null ? prefix : "_parameter.")
                            + IncrementsPlugin.METHOD_GET_INC_MAP + "()." + MyBatis3FormattingUtilities.escapeStringForMyBatis3(introspectedColumn.getActualColumnName()) + ".value} "
                            + MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, prefix));
            when.addElement(spec);
            choose.addElement(when);

            // 启用了增量操作
            XmlElement otherwise = new XmlElement("otherwise");
            TextElement normal = new TextElement(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, prefix));
            otherwise.addElement(normal);
            choose.addElement(otherwise);

            list.add(choose);

            // 3. 结尾逗号
            if (hasComma) {
                list.add(new TextElement(","));
            }
        }

        return list;
    }

    /**
     * 生成增量操作节点(SelectiveEnhancedPlugin)
     * @param versionColumn 需要排除的column（主要是和乐观锁插件整合时要把版本列排除掉）
     * @return
     */
    @Override
    public Element incrementSetsWithSelectiveEnhancedPluginElementGenerated(IntrospectedColumn versionColumn) {
        if (this.support()) {
            XmlElement choose = new XmlElement("choose");

            for (IntrospectedColumn introspectedColumn : this.incColumns) {
                if (versionColumn == null || !introspectedColumn.getActualColumnName().equals(versionColumn.getActualColumnName())) {
                    XmlElement when = new XmlElement("when");

                    // 需要 inc 的列
                    StringBuilder sb = new StringBuilder();
                    sb.append("'");
                    sb.append(introspectedColumn.getActualColumnName());
                    sb.append("'.toString()");
                    sb.append(" == ");
                    sb.append("column.value");

                    when.addAttribute(new Attribute("test", sb.toString()));
                    when.addElement(new TextElement("${column.escapedColumnName} = ${column.escapedColumnName} ${record." + METHOD_GET_INC_MAP + "()."
                            + introspectedColumn.getActualColumnName()
                            + ".value} #{record.${column.javaProperty},jdbcType=${column.jdbcType}}"));
                    choose.addElement(when);
                }
            }

            if (versionColumn == null) {
                XmlElement otherwise = new XmlElement("otherwise");
                otherwise.addElement(new TextElement("${column.escapedColumnName} = #{record.${column.javaProperty},jdbcType=${column.jdbcType}}"));
                choose.addElement(otherwise);
            } else {
                XmlElement when = new XmlElement("when");
                when.addAttribute(new Attribute("test", "column.value != '" + versionColumn.getActualColumnName() + "'.toString()"));

                when.addElement(new TextElement("${column.escapedColumnName} = #{record.${column.javaProperty},jdbcType=${column.jdbcType}}"));

                choose.addElement(when);
            }

            return choose;
        }
        return null;
    }

    // =================================================== 原生方法的支持 ====================================================

    /**
     * 向topLevelClass 添加必要的操作函数
     * @param topLevelClass
     * @param builderCls
     * @param introspectedTable
     * @param withLombok
     */
    private void addIncMethodToTopLevelClass(TopLevelClass topLevelClass, InnerClass builderCls, IntrospectedTable introspectedTable, boolean withLombok) {
        // 增加field
        Field fIncrements = JavaElementGeneratorTools.generateField(
                IncrementsPlugin.FIELD_INC_MAP,
                JavaVisibility.PROTECTED,
                new FullyQualifiedJavaType("Map<String, " + this.getIncEnum(builderCls, introspectedTable).getFullyQualifiedName() + ">"),
                "new HashMap<String, " + this.getIncEnum(builderCls, introspectedTable).getFullyQualifiedName() + ">()"
        );
        fIncrements.setFinal(true);
        commentGenerator.addFieldComment(fIncrements, introspectedTable);
        topLevelClass.addField(fIncrements);
        topLevelClass.addImportedType("java.util.Map");
        topLevelClass.addImportedType("java.util.HashMap");
        // inc map 获取方法
        if (withLombok) {
            topLevelClass.addImportedType(LombokPlugin.EnumLombokAnnotations.ACCESSORS_FLUENT_TRUE.getClazz());
            fIncrements.addAnnotation(LombokPlugin.EnumLombokAnnotations.ACCESSORS_FLUENT_TRUE.getAnnotation());
        } else {
            Method getIncMapMethod = JavaElementGeneratorTools.generateMethod(
                    METHOD_GET_INC_MAP,
                    JavaVisibility.PUBLIC,
                    fIncrements.getType()
            );
            commentGenerator.addGeneralMethodComment(getIncMapMethod, introspectedTable);
            getIncMapMethod.addBodyLine("return this." + FIELD_INC_MAP + ";");
            FormatTools.addMethodWithBestPosition(topLevelClass, getIncMapMethod);
        }
        // 增加判断方法
        Method mHasIncsForColumn = JavaElementGeneratorTools.generateMethod(
                IncrementsPlugin.METHOD_INC_CHECK,
                JavaVisibility.PUBLIC,
                FullyQualifiedJavaType.getBooleanPrimitiveInstance(),
                new Parameter(FullyQualifiedJavaType.getStringInstance(), "column")
        );
        commentGenerator.addGeneralMethodComment(mHasIncsForColumn, introspectedTable);
        mHasIncsForColumn.addBodyLine("return " + IncrementsPlugin.FIELD_INC_MAP + ".get(column) != null;");
        FormatTools.addMethodWithBestPosition(topLevelClass, mHasIncsForColumn);
    }

    /**
     * 生成Inc enum
     * @param introspectedTable
     * @return
     */
    private InnerEnum generatedIncEnum(IntrospectedTable introspectedTable) {
        // 增加枚举
        InnerEnum eIncrements = new InnerEnum(new FullyQualifiedJavaType("Inc"));
        eIncrements.setVisibility(JavaVisibility.PUBLIC);
        eIncrements.setStatic(true);
        eIncrements.addEnumConstant("INC(\"+\")");
        eIncrements.addEnumConstant("DEC(\"-\")");
        commentGenerator.addEnumComment(eIncrements, introspectedTable);
        // 生成属性和构造函数
        Field fValue = new Field("value", FullyQualifiedJavaType.getStringInstance());
        fValue.setVisibility(JavaVisibility.PRIVATE);
        fValue.setFinal(true);
        commentGenerator.addFieldComment(fValue, introspectedTable);
        eIncrements.addField(fValue);

        Method mInc = new Method("Inc");
        mInc.setConstructor(true);
        mInc.addBodyLine("this.value = value;");
        mInc.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "value"));
        commentGenerator.addGeneralMethodComment(mInc, introspectedTable);
        FormatTools.addMethodWithBestPosition(eIncrements, mInc);

        Method mValue = JavaElementGeneratorTools.generateGetterMethod(fValue);
        commentGenerator.addGeneralMethodComment(mValue, introspectedTable);
        FormatTools.addMethodWithBestPosition(eIncrements, mValue);

        return eIncrements;
    }

    /**
     * 有Selective代码生成
     * @param element
     */
    private void generatedWithSelective(XmlElement element, IntrospectedTable introspectedTable, boolean hasPrefix) {
        if (this.support()) {
            // 查找 set->if->text
            List<XmlElement> sets = XmlElementTools.findXmlElements(element, "set");
            if (sets.size() > 0) {
                List<XmlElement> ifs = XmlElementTools.findXmlElements(sets.get(0), "if");
                if (ifs.size() > 0) {
                    for (XmlElement xmlElement : ifs) {
                        // 下面为if的text节点
                        List<Element> textEles = xmlElement.getElements();
                        TextElement textEle = (TextElement) textEles.get(0);
                        String[] strs = textEle.getContent().split("=");
                        String columnName = strs[0].trim();
                        IntrospectedColumn introspectedColumn = IntrospectedTableTools.safeGetColumn(introspectedTable, columnName);
                        // 查找是否需要进行增量操作
                        List<Element> incrementEles = PluginTools.getHook(IIncrementsPluginHook.class).incrementSetElementGenerated(introspectedColumn, hasPrefix ? "record." : null, true);
                        if (!incrementEles.isEmpty()) {
                            xmlElement.getElements().clear();
                            xmlElement.getElements().addAll(incrementEles);
                        }
                    }
                }
            }
        }
    }

    /**
     * 无Selective代码生成
     * @param xmlElement
     * @param introspectedTable
     * @param hasPrefix
     */
    private void generatedWithoutSelective(XmlElement xmlElement, IntrospectedTable introspectedTable, boolean hasPrefix) {
        if (this.support()) {
            List<Element> newEles = new ArrayList<>();
            for (Element ele : xmlElement.getElements()) {
                // 找到text节点且格式为 set xx = xx 或者 xx = xx
                if (ele instanceof TextElement) {
                    String text = ((TextElement) ele).getContent().trim();
                    if (text.matches("(^set\\s)?\\S+\\s?=.*")) {
                        // 清理 set 操作
                        text = text.replaceFirst("^set\\s", "").trim();
                        String columnName = text.split("=")[0].trim();
                        IntrospectedColumn introspectedColumn = IntrospectedTableTools.safeGetColumn(introspectedTable, columnName);
                        // 查找判断是否需要进行节点替换
                        List<Element> incrementEles = PluginTools.getHook(IIncrementsPluginHook.class).incrementSetElementGenerated(introspectedColumn, hasPrefix ? "record." : null, text.endsWith(","));
                        if (!incrementEles.isEmpty()) {
                            newEles.addAll(incrementEles);

                            continue;
                        }
                    }
                }
                newEles.add(ele);
            }

            // 替换节点
            xmlElement.getElements().clear();
            xmlElement.getElements().addAll(newEles);
        }
    }

    /**
     * 获取INC Enum
     * @param builderCls
     * @return
     */
    private FullyQualifiedJavaType getIncEnum(InnerClass builderCls, IntrospectedTable introspectedTable) {
        try {
            return new FullyQualifiedJavaType(BeanUtils.getProperty(this.incEnumBuilder.getType(), "baseShortName").toString() + "." + this.incEnum.getType().getShortName());
        } catch (Exception e) {
            logger.error("获取Inc enum 失败！", e);
        }
        return null;
    }

    /**
     * 是否启用了
     * @return
     */
    private boolean support() {
        return this.incColumns.size() > 0;
    }

    /**
     * 判断是否为需要进行增量操作的column
     * @param searchColumn
     * @return
     */
    private boolean supportColumn(IntrospectedColumn searchColumn) {
        for (IntrospectedColumn column : this.incColumns) {
            if (column.getActualColumnName().equals(searchColumn.getActualColumnName())) {
                return true;
            }
        }
        return false;
    }
}


/*
 * Copyright (c) 2017.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.itfsw.mybatis.generator.plugins;

import com.itfsw.mybatis.generator.plugins.utils.*;
import com.itfsw.mybatis.generator.plugins.utils.hook.ISelectOneByExamplePluginHook;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;

import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;

/**
 * ---------------------------------------------------------------------------
 * 增加查询一条数据方法
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2016/12/28 14:56
 * ---------------------------------------------------------------------------
 */
public class SelectOneByExamplePlugin extends BasePlugin {
    public static final String METHOD_SELECT_ONE_BY_EXAMPLE = "selectOneByExample";  // 方法名
    public static final String METHOD_SELECT_ONE_BY_EXAMPLE_WITH_BLOBS = "selectOneByExampleWithBLOBs";  // 方法名
    private XmlElement selectOneByExampleEle;
    private XmlElement selectOneByExampleWithBLOBsEle;

    @Override
    public void initialized(IntrospectedTable introspectedTable) {
        super.initialized(introspectedTable);
        // bug:26,27
        this.selectOneByExampleWithBLOBsEle = null;
        this.selectOneByExampleEle = null;
    }

    /**
     * Java Client Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param method
     * @param interfaze
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean clientSelectByExampleWithBLOBsMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        // 方法生成 selectOneByExample
        Method selectOneMethod = JavaElementGeneratorTools.generateMethod(
                METHOD_SELECT_ONE_BY_EXAMPLE_WITH_BLOBS,
                JavaVisibility.DEFAULT,
                JavaElementGeneratorTools.getModelTypeWithBLOBs(introspectedTable),
                new Parameter(new FullyQualifiedJavaType(introspectedTable.getExampleType()), "example")
        );
        commentGenerator.addGeneralMethodComment(selectOneMethod, introspectedTable);

        // hook
        if (PluginTools.getHook(ISelectOneByExamplePluginHook.class).clientSelectOneByExampleWithBLOBsMethodGenerated(selectOneMethod, interfaze, introspectedTable)) {
            // interface 增加方法
            FormatTools.addMethodWithBestPosition(interfaze, selectOneMethod);
            logger.debug("itfsw(查询单条数据插件):" + interfaze.getType().getShortName() + "增加selectOneByExampleWithBLOBs方法。");
        }
        return super.clientSelectByExampleWithBLOBsMethodGenerated(method, interfaze, introspectedTable);
    }

    /**
     * Java Client Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param method
     * @param interfaze
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean clientSelectByExampleWithoutBLOBsMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        // 方法生成 selectOneByExample
        Method selectOneMethod = JavaElementGeneratorTools.generateMethod(
                METHOD_SELECT_ONE_BY_EXAMPLE,
                JavaVisibility.DEFAULT,
                JavaElementGeneratorTools.getModelTypeWithoutBLOBs(introspectedTable),
                new Parameter(new FullyQualifiedJavaType(introspectedTable.getExampleType()), "example")
        );
        commentGenerator.addGeneralMethodComment(selectOneMethod, introspectedTable);

        // hook
        if (PluginTools.getHook(ISelectOneByExamplePluginHook.class).clientSelectOneByExampleWithoutBLOBsMethodGenerated(selectOneMethod, interfaze, introspectedTable)) {
            // interface 增加方法
            FormatTools.addMethodWithBestPosition(interfaze, selectOneMethod);
            logger.debug("itfsw(查询单条数据插件):" + interfaze.getType().getShortName() + "增加selectOneByExample方法。");
        }
        return super.clientSelectByExampleWithoutBLOBsMethodGenerated(selectOneMethod, interfaze, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        // ------------------------------------ selectOneByExample ----------------------------------
        // 生成查询语句
        XmlElement selectOneElement = new XmlElement("select");
        // 添加注释(!!!必须添加注释，overwrite覆盖生成时，@see XmlFileMergerJaxp.isGeneratedNode会去判断注释中是否存在OLD_ELEMENT_TAGS中的一点，例子：@mbg.generated)
        commentGenerator.addComment(selectOneElement);

        // 添加ID
        selectOneElement.addAttribute(new Attribute("id", METHOD_SELECT_ONE_BY_EXAMPLE));
        // 添加返回类型
        selectOneElement.addAttribute(new Attribute("resultMap", introspectedTable.getBaseResultMapId()));
        // 添加参数类型
        selectOneElement.addAttribute(new Attribute("parameterType", introspectedTable.getExampleType()));
        selectOneElement.addElement(new TextElement("select"));

        StringBuilder sb = new StringBuilder();
        if (stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
            sb.append('\'');
            sb.append(introspectedTable.getSelectByExampleQueryId());
            sb.append("' as QUERYID,");
            selectOneElement.addElement(new TextElement(sb.toString()));
        }
        selectOneElement.addElement(XmlElementGeneratorTools.getBaseColumnListElement(introspectedTable));

        sb.setLength(0);
        sb.append("from ");
        sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
        selectOneElement.addElement(new TextElement(sb.toString()));
        selectOneElement.addElement(XmlElementGeneratorTools.getExampleIncludeElement(introspectedTable));

        XmlElement ifElement = new XmlElement("if");
        ifElement.addAttribute(new Attribute("test", "orderByClause != null"));  //$NON-NLS-2$
        ifElement.addElement(new TextElement("order by ${orderByClause}"));
        selectOneElement.addElement(ifElement);

        // 只查询一条
        selectOneElement.addElement(new TextElement("limit 1"));
        this.selectOneByExampleEle = selectOneElement;
        return super.sqlMapSelectByExampleWithoutBLOBsElementGenerated(element, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapSelectByExampleWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        // 生成查询语句
        XmlElement selectOneWithBLOBsElement = new XmlElement("select");
        // 添加注释(!!!必须添加注释，overwrite覆盖生成时，@see XmlFileMergerJaxp.isGeneratedNode会去判断注释中是否存在OLD_ELEMENT_TAGS中的一点，例子：@mbg.generated)
        commentGenerator.addComment(selectOneWithBLOBsElement);

        // 添加ID
        selectOneWithBLOBsElement.addAttribute(new Attribute("id", METHOD_SELECT_ONE_BY_EXAMPLE_WITH_BLOBS));
        // 添加返回类型
        selectOneWithBLOBsElement.addAttribute(new Attribute("resultMap", introspectedTable.getResultMapWithBLOBsId()));
        // 添加参数类型
        selectOneWithBLOBsElement.addAttribute(new Attribute("parameterType", introspectedTable.getExampleType()));
        // 添加查询SQL
        selectOneWithBLOBsElement.addElement(new TextElement("select"));

        StringBuilder sb = new StringBuilder();
        if (stringHasValue(introspectedTable.getSelectByExampleQueryId())) {
            sb.append('\'');
            sb.append(introspectedTable.getSelectByExampleQueryId());
            sb.append("' as QUERYID,");
            selectOneWithBLOBsElement.addElement(new TextElement(sb.toString()));
        }

        selectOneWithBLOBsElement.addElement(XmlElementGeneratorTools.getBaseColumnListElement(introspectedTable));
        selectOneWithBLOBsElement.addElement(new TextElement(","));
        selectOneWithBLOBsElement.addElement(XmlElementGeneratorTools.getBlobColumnListElement(introspectedTable));

        sb.setLength(0);
        sb.append("from ");
        sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
        selectOneWithBLOBsElement.addElement(new TextElement(sb.toString()));
        selectOneWithBLOBsElement.addElement(XmlElementGeneratorTools.getExampleIncludeElement(introspectedTable));

        XmlElement ifElement1 = new XmlElement("if");
        ifElement1.addAttribute(new Attribute("test", "orderByClause != null"));  //$NON-NLS-2$
        ifElement1.addElement(new TextElement("order by ${orderByClause}"));
        selectOneWithBLOBsElement.addElement(ifElement1);

        // 只查询一条
        selectOneWithBLOBsElement.addElement(new TextElement("limit 1"));

        this.selectOneByExampleWithBLOBsEle = selectOneWithBLOBsElement;
        return super.sqlMapSelectByExampleWithBLOBsElementGenerated(element, introspectedTable);
    }

    /**
     * SQL Map Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param document
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
        if (selectOneByExampleEle != null) {
            // hook
            if (PluginTools.getHook(ISelectOneByExamplePluginHook.class).sqlMapSelectOneByExampleWithoutBLOBsElementGenerated(document, selectOneByExampleEle, introspectedTable)) {
                // 添加到根节点
                FormatTools.addElementWithBestPosition(document.getRootElement(), selectOneByExampleEle);
                logger.debug("itfsw(查询单条数据插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加selectOneByExample方法。");
            }
        }

        if (selectOneByExampleWithBLOBsEle != null) {
            // hook
            if (PluginTools.getHook(ISelectOneByExamplePluginHook.class).sqlMapSelectOneByExampleWithBLOBsElementGenerated(document, selectOneByExampleWithBLOBsEle, introspectedTable)) {
                // 添加到根节点
                FormatTools.addElementWithBestPosition(document.getRootElement(), selectOneByExampleWithBLOBsEle);
                logger.debug("itfsw(查询单条数据插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加selectOneByExampleWithBLOBs方法。");
            }
        }

        return true;
    }
}


/*
 * Copyright (c) 2018.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.itfsw.mybatis.generator.plugins.utils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * ---------------------------------------------------------------------------
 *
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2018/5/3 18:39
 * ---------------------------------------------------------------------------
 */
public class BeanUtils {
    /**
     * 设置属性
     * @param bean
     * @param name
     * @param value
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    public static void setProperty(final Object bean, final String name, final Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = bean.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(bean, value);
    }

    /**
     * 获取属性
     * @param bean
     * @param name
     * @return
     */
    public static Object getProperty(final Object bean, final String name) throws NoSuchFieldException, IllegalAccessException {
        Field field = bean.getClass().getDeclaredField(name);
        field.setAccessible(true);
        return field.get(bean);
    }

    /**
     * 执行无参方法
     * @param bean
     * @param clazz
     * @param name
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public static Object invoke(final Object bean, Class clazz, final String name) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = clazz.getDeclaredMethod(name);
        method.setAccessible(true);
        return method.invoke(bean);
    }
}