/*
 * 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.BasePlugin;
import com.itfsw.mybatis.generator.plugins.utils.FormatTools;
import com.itfsw.mybatis.generator.plugins.utils.JavaElementGeneratorTools;
import com.itfsw.mybatis.generator.plugins.utils.hook.ISelectSelectivePluginHook;
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 org.mybatis.generator.internal.util.StringUtility;

import java.util.List;

/**
 * ---------------------------------------------------------------------------
 * 增加分页方法
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2016/12/29 18:14
 * ---------------------------------------------------------------------------
 */
public class LimitPlugin extends BasePlugin implements ISelectSelectivePluginHook {
    /**
     * 分页开始页码
     */
    public final static String PRO_START_PAGE = "startPage";
    private final static int DEFAULT_START_PAGE = 0;
    private int startPage;

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean validate(List<String> warnings) {
        // 该插件只支持MYSQL
        if ("com.mysql.jdbc.Driver".equalsIgnoreCase(this.getContext().getJdbcConnectionConfiguration().getDriverClass()) == false
                && "com.mysql.cj.jdbc.Driver".equalsIgnoreCase(this.getContext().getJdbcConnectionConfiguration().getDriverClass()) == false) {
            warnings.add("itfsw:插件" + this.getClass().getTypeName() + "只支持MySQL数据库！");
            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);

        // 获取配置
        String startPage = this.getProperties().getProperty(LimitPlugin.PRO_START_PAGE);
        if (StringUtility.stringHasValue(startPage)) {
            this.startPage = Integer.valueOf(startPage);
        } else {
            this.startPage = DEFAULT_START_PAGE;
        }
    }

    /**
     * ModelExample Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        PrimitiveTypeWrapper integerWrapper = FullyQualifiedJavaType.getIntInstance().getPrimitiveTypeWrapper();
        // 添加offset和rows字段
        Field offsetField = JavaElementGeneratorTools.generateField(
                "offset",
                JavaVisibility.PROTECTED,
                integerWrapper,
                null
        );
        commentGenerator.addFieldComment(offsetField, introspectedTable);
        topLevelClass.addField(offsetField);

        Field rowsField = JavaElementGeneratorTools.generateField(
                "rows",
                JavaVisibility.PROTECTED,
                integerWrapper,
                null
        );
        commentGenerator.addFieldComment(rowsField, introspectedTable);
        topLevelClass.addField(rowsField);
        logger.debug("itfsw(MySQL分页插件):" + topLevelClass.getType().getShortName() + "增加offset和rows字段");

        // 增加getter && setter 方法
        Method mSetOffset = JavaElementGeneratorTools.generateSetterMethod(offsetField);
        commentGenerator.addGeneralMethodComment(mSetOffset, introspectedTable);
        FormatTools.addMethodWithBestPosition(topLevelClass, mSetOffset);

        Method mGetOffset = JavaElementGeneratorTools.generateGetterMethod(offsetField);
        commentGenerator.addGeneralMethodComment(mGetOffset, introspectedTable);
        FormatTools.addMethodWithBestPosition(topLevelClass, mGetOffset);

        Method mSetRows = JavaElementGeneratorTools.generateSetterMethod(rowsField);
        commentGenerator.addGeneralMethodComment(mSetRows, introspectedTable);
        FormatTools.addMethodWithBestPosition(topLevelClass, mSetRows);

        Method mGetRows = JavaElementGeneratorTools.generateGetterMethod(rowsField);
        commentGenerator.addGeneralMethodComment(mGetRows, introspectedTable);
        FormatTools.addMethodWithBestPosition(topLevelClass, mGetRows);
        logger.debug("itfsw(MySQL分页插件):" + topLevelClass.getType().getShortName() + "增加offset和rows的getter和setter实现。");

        // 提供几个快捷方法
        Method setLimit = JavaElementGeneratorTools.generateMethod(
                "limit",
                JavaVisibility.PUBLIC,
                topLevelClass.getType(),
                new Parameter(integerWrapper, "rows")
        );
        commentGenerator.addGeneralMethodComment(setLimit, introspectedTable);
        setLimit = JavaElementGeneratorTools.generateMethodBody(
                setLimit,
                "this.rows = rows;",
                "return this;"
        );
        FormatTools.addMethodWithBestPosition(topLevelClass, setLimit);

        Method setLimit2 = JavaElementGeneratorTools.generateMethod(
                "limit",
                JavaVisibility.PUBLIC,
                topLevelClass.getType(),
                new Parameter(integerWrapper, "offset"),
                new Parameter(integerWrapper, "rows")
        );
        commentGenerator.addGeneralMethodComment(setLimit2, introspectedTable);
        setLimit2 = JavaElementGeneratorTools.generateMethodBody(
                setLimit2,
                "this.offset = offset;",
                "this.rows = rows;",
                "return this;"
        );
        FormatTools.addMethodWithBestPosition(topLevelClass, setLimit2);
        logger.debug("itfsw(MySQL分页插件):" + topLevelClass.getType().getShortName() + "增加limit方法。");

        Method setPage = JavaElementGeneratorTools.generateMethod(
                "page",
                JavaVisibility.PUBLIC,
                topLevelClass.getType(),
                new Parameter(integerWrapper, "page"),
                new Parameter(integerWrapper, "pageSize")
        );
        commentGenerator.addGeneralMethodComment(setPage, introspectedTable);
        setPage = JavaElementGeneratorTools.generateMethodBody(
                setPage,
                "this.offset = " + (this.startPage == 0 ? "page" : "(page - " + this.startPage + ")") + " * pageSize;",
                "this.rows = pageSize;",
                "return this;"
        );
        FormatTools.addMethodWithBestPosition(topLevelClass, setPage);
        logger.debug("itfsw(MySQL分页插件):" + topLevelClass.getType().getShortName() + "增加page方法");

        // !!! clear 方法增加 offset 和 rows的清理
        List<Method> methodList = topLevelClass.getMethods();
        for (Method method : methodList) {
            if (method.getName().equals("clear")) {
                method.addBodyLine("rows = null;");
                method.addBodyLine("offset = null;");
                logger.debug("itfsw(MySQL分页插件):" + topLevelClass.getType().getShortName() + "修改clear方法,增加rows和offset字段的清空");
            }
        }

        return true;
    }

    /**
     * SQL Map Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        this.generateLimitElement(element);
        return true;
    }

    /**
     * SQL Map Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapSelectByExampleWithBLOBsElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        this.generateLimitElement(element);
        return true;
    }

    // ============================================ ISelectSelectivePluginHook ==========================================

    @Override
    public boolean sqlMapSelectByExampleSelectiveElementGenerated(Document document, XmlElement element, IntrospectedTable introspectedTable) {
        this.generateLimitElementWithExample(element);
        return false;
    }

    /**
     * 生成limit节点
     * @param element
     */
    private void generateLimitElement(XmlElement element) {
        XmlElement ifLimitNotNullElement = new XmlElement("if");
        ifLimitNotNullElement.addAttribute(new Attribute("test", "rows != null"));

        XmlElement ifOffsetNotNullElement = new XmlElement("if");
        ifOffsetNotNullElement.addAttribute(new Attribute("test", "offset != null"));
        ifOffsetNotNullElement.addElement(new TextElement("limit ${offset}, ${rows}"));
        ifLimitNotNullElement.addElement(ifOffsetNotNullElement);

        XmlElement ifOffsetNullElement = new XmlElement("if");
        ifOffsetNullElement.addAttribute(new Attribute("test", "offset == null"));
        ifOffsetNullElement.addElement(new TextElement("limit ${rows}"));
        ifLimitNotNullElement.addElement(ifOffsetNullElement);

        element.addElement(ifLimitNotNullElement);
    }

    /**
     * 生成limit节点
     * @param element
     */
    private void generateLimitElementWithExample(XmlElement element) {
        XmlElement ifLimitNotNullElement = new XmlElement("if");
        ifLimitNotNullElement.addAttribute(new Attribute("test", "example.rows != null"));

        XmlElement ifOffsetNotNullElement = new XmlElement("if");
        ifOffsetNotNullElement.addAttribute(new Attribute("test", "example.offset != null"));
        ifOffsetNotNullElement.addElement(new TextElement("limit ${example.offset}, ${example.rows}"));
        ifLimitNotNullElement.addElement(ifOffsetNotNullElement);

        XmlElement ifOffsetNullElement = new XmlElement("if");
        ifOffsetNullElement.addAttribute(new Attribute("test", "example.offset == null"));
        ifOffsetNullElement.addElement(new TextElement("limit ${example.rows}"));
        ifLimitNotNullElement.addElement(ifOffsetNullElement);

        element.addElement(ifLimitNotNullElement);
    }
}


/*
 * 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.utils.enhanced;

import org.mybatis.generator.api.dom.OutputUtilities;
import org.mybatis.generator.api.dom.java.*;

import java.util.Iterator;
import java.util.Set;

import static org.mybatis.generator.api.dom.OutputUtilities.*;
import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;

/**
 * ---------------------------------------------------------------------------
 * 内部接口
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/1/12 17:53
 * ---------------------------------------------------------------------------
 */
public class InnerInterface extends Interface{
    public InnerInterface(FullyQualifiedJavaType type) {
        super(type);
    }

    public InnerInterface(String type) {
        super(type);
    }

    /**
     * 格式化后内容，因为是内部接口，需要增加缩进
     *
     * @param indentLevel the indent level
     * @param compilationUnit the compilation unit
     * @return the formatted content
     */
    @Override
    public String getFormattedContent(int indentLevel, CompilationUnit compilationUnit) {
        StringBuilder sb = new StringBuilder();

        for (String commentLine : getFileCommentLines()) {
            sb.append(commentLine);
            newLine(sb);
        }

        if (stringHasValue(getType().getPackageName())) {
            sb.append("package ");
            sb.append(getType().getPackageName());
            sb.append(';');
            newLine(sb);
            newLine(sb);
        }

        for (String staticImport : getStaticImports()) {
            sb.append("import static ");
            sb.append(staticImport);
            sb.append(';');
            newLine(sb);
        }

        if (getStaticImports().size() > 0) {
            newLine(sb);
        }

        Set<String> importStrings = calculateImports(getImportedTypes());
        for (String importString : importStrings) {
            sb.append(importString);
            newLine(sb);
        }

        if (importStrings.size() > 0) {
            newLine(sb);
        }

        addFormattedJavadoc(sb, indentLevel);
        addFormattedAnnotations(sb, indentLevel);

        OutputUtilities.javaIndent(sb, indentLevel);

        sb.append(getVisibility().getValue());

        if (isFinal()) {
            sb.append("final ");
        }

        sb.append("interface ");
        sb.append(getType().getShortName());

        if (getSuperInterfaceTypes().size() > 0) {
            sb.append(" extends ");

            boolean comma = false;
            for (FullyQualifiedJavaType fqjt : getSuperInterfaceTypes()) {
                if (comma) {
                    sb.append(", ");
                } else {
                    comma = true;
                }

                sb.append(JavaDomUtils.calculateTypeName(this, fqjt));
            }
        }

        sb.append(" {");
        indentLevel++;

        Iterator<Method> mtdIter = getMethods().iterator();
        while (mtdIter.hasNext()) {
            newLine(sb);
            Method method = mtdIter.next();
            sb.append(method.getFormattedContent(indentLevel, true, this));
            if (mtdIter.hasNext()) {
                newLine(sb);
            }
        }

        indentLevel--;
        newLine(sb);
        javaIndent(sb, indentLevel);
        sb.append('}');

        return sb.toString();
    }
}


/*
 * 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.utils;

import com.itfsw.mybatis.generator.plugins.ExampleTargetPlugin;
import org.mybatis.generator.api.FullyQualifiedTable;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.JavaModelGeneratorConfiguration;
import org.mybatis.generator.config.PluginConfiguration;
import org.mybatis.generator.internal.util.StringUtility;

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

/**
 * ---------------------------------------------------------------------------
 * IntrospectedTable 的一些拓展增强
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/6/13 13:48
 * ---------------------------------------------------------------------------
 */
public class IntrospectedTableTools {

    /**
     * 设置DomainObjectName和MapperName
     * @param introspectedTable
     * @param context
     * @param domainObjectName
     */
    public static void setDomainObjectName(IntrospectedTable introspectedTable, Context context, String domainObjectName) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        // 配置信息（没啥用）
        introspectedTable.getTableConfiguration().setDomainObjectName(domainObjectName);

        // FullyQualifiedTable修正
        Field domainObjectNameField = FullyQualifiedTable.class.getDeclaredField("domainObjectName");
        domainObjectNameField.setAccessible(true);
        domainObjectNameField.set(introspectedTable.getFullyQualifiedTable(), domainObjectName);

        // 重新修正introspectedTable属性信息
        Method calculateJavaClientAttributes = IntrospectedTable.class.getDeclaredMethod("calculateJavaClientAttributes");
        calculateJavaClientAttributes.setAccessible(true);
        calculateJavaClientAttributes.invoke(introspectedTable);

        Method calculateModelAttributes = IntrospectedTable.class.getDeclaredMethod("calculateModelAttributes");
        calculateModelAttributes.setAccessible(true);
        calculateModelAttributes.invoke(introspectedTable);

        Method calculateXmlAttributes = IntrospectedTable.class.getDeclaredMethod("calculateXmlAttributes");
        calculateXmlAttributes.setAccessible(true);
        calculateXmlAttributes.invoke(introspectedTable);

        // 注意！！ 如果配置了ExampleTargetPlugin插件，要修正Example 位置
        PluginConfiguration configuration = PluginTools.getPluginConfiguration(context, ExampleTargetPlugin.class);
        if (configuration != null && configuration.getProperty(ExampleTargetPlugin.PRO_TARGET_PACKAGE) != null) {
            String exampleType = introspectedTable.getExampleType();
            // 修改包名
            JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = context.getJavaModelGeneratorConfiguration();
            String targetPackage = javaModelGeneratorConfiguration.getTargetPackage();
            String newExampleType = exampleType.replace(targetPackage, configuration.getProperty(ExampleTargetPlugin.PRO_TARGET_PACKAGE));

            introspectedTable.setExampleType(newExampleType);
        }
    }

    /**
     * 安全获取column 通过正则获取的name可能包含beginningDelimiter&&endingDelimiter
     * @param introspectedTable
     * @param columnName
     * @return
     */
    public static IntrospectedColumn safeGetColumn(IntrospectedTable introspectedTable, String columnName) {
        // columnName
        columnName = columnName.trim();
        // 过滤
        String beginningDelimiter = introspectedTable.getContext().getBeginningDelimiter();
        if (StringUtility.stringHasValue(beginningDelimiter)) {
            columnName = columnName.replaceFirst("^" + beginningDelimiter, "");
        }
        String endingDelimiter = introspectedTable.getContext().getEndingDelimiter();
        if (StringUtility.stringHasValue(endingDelimiter)) {
            columnName = columnName.replaceFirst(endingDelimiter + "$", "");
        }

        return introspectedTable.getColumn(columnName);
    }

    /**
     * 获取生成model baseRecord的列
     * @param introspectedTable
     * @return
     */
    public static List<IntrospectedColumn> getModelBaseRecordClomns(IntrospectedTable introspectedTable) {
        List<IntrospectedColumn> introspectedColumns;
        if (includePrimaryKeyColumns(introspectedTable)) {
            if (includeBLOBColumns(introspectedTable)) {
                introspectedColumns = introspectedTable.getAllColumns();
            } else {
                introspectedColumns = introspectedTable.getNonBLOBColumns();
            }
        } else {
            if (includeBLOBColumns(introspectedTable)) {
                introspectedColumns = introspectedTable
                        .getNonPrimaryKeyColumns();
            } else {
                introspectedColumns = introspectedTable.getBaseColumns();
            }
        }

        return introspectedColumns;
    }

    /**
     * 是否有primaryKey 列
     * @param introspectedTable
     * @return
     */
    public static boolean includePrimaryKeyColumns(IntrospectedTable introspectedTable) {
        return !introspectedTable.getRules().generatePrimaryKeyClass()
                && introspectedTable.hasPrimaryKeyColumns();
    }

    /**
     * 是否有 blob 列
     * @param introspectedTable
     * @return
     */
    public static boolean includeBLOBColumns(IntrospectedTable introspectedTable) {
        return !introspectedTable.getRules().generateRecordWithBLOBsClass()
                && introspectedTable.hasBLOBColumns();
    }
}


/*
 * 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.BasePlugin;
import com.itfsw.mybatis.generator.plugins.utils.IntrospectedTableTools;
import com.itfsw.mybatis.generator.plugins.utils.PluginTools;
import com.itfsw.mybatis.generator.plugins.utils.hook.ILombokPluginHook;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.internal.util.StringUtility;

import java.util.List;
import java.util.Properties;

/**
 * ---------------------------------------------------------------------------
 * LombokPlugin
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2018/10/29 14:33
 * ---------------------------------------------------------------------------
 */
public class LombokPlugin extends BasePlugin {
    public static final String PRO_BUILDER = "@Builder";  // 是否支持 Builder 注解
    public static final String PRO_ALL_ARGS_CONSTRUCTOR = "@AllArgsConstructor"; // 是否支持 AllArgsConstructor 注解
    public static final String PRO_NO_ARGS_CONSTRUCTOR = "@NoArgsConstructor"; // 是否支持 NoArgsConstructor 注解

    private boolean hasBuilder;
    private boolean hasAllArgsConstructor;
    private boolean hasNoArgsConstructor;

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

        String builder = properties.getProperty(PRO_BUILDER);
        String allArgsConstructor = properties.getProperty(PRO_ALL_ARGS_CONSTRUCTOR);
        String noArgsConstructor = properties.getProperty(PRO_NO_ARGS_CONSTRUCTOR);

        this.hasBuilder = builder == null ? false : StringUtility.isTrue(builder);
        this.hasAllArgsConstructor = allArgsConstructor == null ? false : StringUtility.isTrue(allArgsConstructor);
        this.hasNoArgsConstructor = noArgsConstructor == null ? false : StringUtility.isTrue(noArgsConstructor);

        return super.validate(warnings);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        // @Data
        this.addAnnotations(topLevelClass, EnumLombokAnnotations.DATA);
        if (topLevelClass.getSuperClass() != null) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.EQUALS_AND_HASH_CODE_CALL_SUPER);
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.TO_STRING_CALL_SUPER);
        }

        // @Builder
        List<IntrospectedColumn> columns = IntrospectedTableTools.getModelBaseRecordClomns(introspectedTable);
        if (this.hasBuilder && PluginTools.getHook(ILombokPluginHook.class).modelBaseRecordBuilderClassGenerated(topLevelClass, columns, introspectedTable)) {
            // 有子类或者父类
            if (introspectedTable.getRules().generateRecordWithBLOBsClass() || introspectedTable.getRules().generatePrimaryKeyClass() || topLevelClass.getSuperClass() != null) {
                this.addAnnotations(topLevelClass, EnumLombokAnnotations.SUPER_BUILDER);
            } else {
                this.addAnnotations(topLevelClass, EnumLombokAnnotations.BUILDER);
            }
        }

        // @Constructor
        if (this.hasNoArgsConstructor) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.NO_ARGS_CONSTRUCTOR);
        }
        if (this.hasAllArgsConstructor) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.ALL_ARGS_CONSTRUCTOR);
        }
        return super.modelBaseRecordClassGenerated(topLevelClass, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        // @Data
        this.addAnnotations(topLevelClass, EnumLombokAnnotations.DATA);
        if (topLevelClass.getSuperClass() != null) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.EQUALS_AND_HASH_CODE_CALL_SUPER);
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.TO_STRING_CALL_SUPER);
        }

        // @Builder
        List<IntrospectedColumn> columns = introspectedTable.getPrimaryKeyColumns();
        if (this.hasBuilder && PluginTools.getHook(ILombokPluginHook.class).modelPrimaryKeyBuilderClassGenerated(topLevelClass, columns, introspectedTable)) {
            // 有子类或者父类
            if (introspectedTable.getRules().generateRecordWithBLOBsClass() || introspectedTable.getRules().generateBaseRecordClass() || topLevelClass.getSuperClass() != null) {
                this.addAnnotations(topLevelClass, EnumLombokAnnotations.SUPER_BUILDER);
            } else {
                this.addAnnotations(topLevelClass, EnumLombokAnnotations.BUILDER);
            }
        }

        // @Constructor
        if (this.hasNoArgsConstructor) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.NO_ARGS_CONSTRUCTOR);
        }
        if (this.hasAllArgsConstructor) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.ALL_ARGS_CONSTRUCTOR);
        }
        return super.modelPrimaryKeyClassGenerated(topLevelClass, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelRecordWithBLOBsClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        // @Data
        this.addAnnotations(topLevelClass, EnumLombokAnnotations.DATA);
        if (topLevelClass.getSuperClass() != null) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.EQUALS_AND_HASH_CODE_CALL_SUPER);
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.TO_STRING_CALL_SUPER);
        }

        // @Builder
        List<IntrospectedColumn> columns = introspectedTable.getBLOBColumns();
        if (this.hasBuilder && PluginTools.getHook(ILombokPluginHook.class).modelRecordWithBLOBsBuilderClassGenerated(topLevelClass, columns, introspectedTable)) {
            // 有子类或者父类
            if (introspectedTable.getRules().generateBaseRecordClass() || introspectedTable.getRules().generatePrimaryKeyClass() || topLevelClass.getSuperClass() != null) {
                this.addAnnotations(topLevelClass, EnumLombokAnnotations.SUPER_BUILDER);
            } else {
                this.addAnnotations(topLevelClass, EnumLombokAnnotations.BUILDER);
            }
        }

        // @Constructor
        if (this.hasNoArgsConstructor) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.NO_ARGS_CONSTRUCTOR);
        }
        if (this.hasAllArgsConstructor) {
            this.addAnnotations(topLevelClass, EnumLombokAnnotations.ALL_ARGS_CONSTRUCTOR);
        }
        return super.modelRecordWithBLOBsClassGenerated(topLevelClass, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param method
     * @param topLevelClass
     * @param introspectedColumn
     * @param introspectedTable
     * @param modelClassType
     * @return
     */
    @Override
    public boolean modelGetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        return false;
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param method
     * @param topLevelClass
     * @param introspectedColumn
     * @param introspectedTable
     * @param modelClassType
     * @return
     */
    @Override
    public boolean modelSetterMethodGenerated(Method method, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        return false;
    }

    /**
     * 添加注解
     * @param topLevelClass
     * @param annotations
     */
    private void addAnnotations(TopLevelClass topLevelClass, EnumLombokAnnotations annotations) {
        topLevelClass.addImportedType(annotations.getClazz());
        topLevelClass.addAnnotation(annotations.getAnnotation());
    }

    /**
     * lombok 类型
     */
    public enum EnumLombokAnnotations {
        DATA("@Data", "lombok.Data"),
        BUILDER("@Builder", "lombok.Builder"),
        SUPER_BUILDER("@SuperBuilder", "lombok.experimental.SuperBuilder"),
        ALL_ARGS_CONSTRUCTOR("@AllArgsConstructor", "lombok.AllArgsConstructor"),
        NO_ARGS_CONSTRUCTOR("@NoArgsConstructor", "lombok.NoArgsConstructor"),
        EQUALS_AND_HASH_CODE_CALL_SUPER("@EqualsAndHashCode(callSuper = true)", "lombok.EqualsAndHashCode"),
        SETTER("@Setter", "lombok.Setter"),
        ACCESSORS_FLUENT_TRUE("@Accessors(fluent = true)", "lombok.experimental.Accessors"),
        TO_STRING("@ToString", "lombok.ToString"),
        TO_STRING_CALL_SUPER("@ToString(callSuper = true)", "lombok.ToString");

        private final String annotation;
        private final String clazz;

        EnumLombokAnnotations(String annotation, String clazz) {
            this.annotation = annotation;
            this.clazz = clazz;
        }

        /**
         * Getter method for property <tt>annotation</tt>.
         * @return property value of annotation
         * @author hewei
         */
        public String getAnnotation() {
            return annotation;
        }

        /**
         * Getter method for property <tt>clazz</tt>.
         * @return property value of clazz
         * @author hewei
         */
        public String getClazz() {
            return clazz;
        }
    }
}


/*
 * 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.BasePlugin;
import com.itfsw.mybatis.generator.plugins.utils.IntrospectedTableTools;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.config.TableConfiguration;

import java.util.List;

/**
 * ---------------------------------------------------------------------------
 * table前缀，解决多数据源表重名问题插件（为Model、Mapper、Example、xml等增加前缀）
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/5/18 13:54
 * ---------------------------------------------------------------------------
 */
@Deprecated
public class TablePrefixPlugin extends BasePlugin {

    public static final String PRO_PREFIX = "prefix";  // 前缀 property
    private String prefix;  // 前缀

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean validate(List<String> warnings) {
        // 如果table配置了domainObjectName或者mapperName就不要再启动该插件了
        for (TableConfiguration tableConfiguration : context.getTableConfigurations()) {
            if (tableConfiguration.getDomainObjectName() != null || tableConfiguration.getMapperName() != null) {
                warnings.add("itfsw:插件" + this.getClass().getTypeName() + "插件请不要配合table的domainObjectName或者mapperName一起使用！");
                return false;
            }
        }

        return super.validate(warnings);
    }

    /**
     * 初始化阶段
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param introspectedTable
     * @return
     */
    @Override
    public void initialized(IntrospectedTable introspectedTable) {
        // 1. 首先获取全局配置
        this.prefix = getProperties().getProperty(PRO_PREFIX);
        // 2. 获取每个table 具体的
        if (introspectedTable.getTableConfigurationProperty(PRO_PREFIX) != null) {
            this.prefix = introspectedTable.getTableConfigurationProperty(PRO_PREFIX);
        }
        // 3. 判断是否配置了前缀
        // !!! TableRenamePlugin 插件的 tableOverride 优先级最高
        if (this.prefix != null && introspectedTable.getTableConfigurationProperty(TableRenamePlugin.PRO_TABLE_OVERRIDE) == null) {
            String domainObjectName = introspectedTable.getFullyQualifiedTable().getDomainObjectName();
            domainObjectName = prefix + domainObjectName;
            try {
                IntrospectedTableTools.setDomainObjectName(introspectedTable, getContext(), domainObjectName);
            } catch (Exception e) {
                logger.error("itfsw:插件" + this.getClass().getTypeName() + "使用prefix替换时异常！", e);
            }
        }
        super.initialized(introspectedTable);
    }
}


/*
 * 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.IIncrementsPluginHook;
import com.itfsw.mybatis.generator.plugins.utils.hook.IOptimisticLockerPluginHook;
import com.itfsw.mybatis.generator.plugins.utils.hook.IUpsertPluginHook;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
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.ListUtilities;
import org.mybatis.generator.config.GeneratedKey;

import java.util.List;

/**
 * ---------------------------------------------------------------------------
 * Selective 增强插件
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2018/4/20 15:39
 * ---------------------------------------------------------------------------
 */
public class SelectiveEnhancedPlugin extends BasePlugin implements IUpsertPluginHook, IOptimisticLockerPluginHook {

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean validate(List<String> warnings) {

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

        return super.validate(warnings);
    }

    /**
     * insertSelective 方法生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param method
     * @param interfaze
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean clientInsertSelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        method.getParameters().clear();

        FullyQualifiedJavaType parameterType = introspectedTable.getRules().calculateAllFieldsClass();
        method.addParameter(new Parameter(parameterType, "record", "@Param(\"record\")"));

        // 找出全字段对应的Model
        FullyQualifiedJavaType fullFieldModel = introspectedTable.getRules().calculateAllFieldsClass();
        // column枚举
        FullyQualifiedJavaType selectiveType = new FullyQualifiedJavaType(fullFieldModel.getShortName() + "." + ModelColumnPlugin.ENUM_NAME);
        method.addParameter(new Parameter(selectiveType, "selective", "@Param(\"selective\")", true));

        FormatTools.replaceGeneralMethodComment(commentGenerator, method, introspectedTable);

        return super.clientInsertSelectiveMethodGenerated(method, interfaze, introspectedTable);
    }

    /**
     * updateByExampleSelective
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param method
     * @param interfaze
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean clientUpdateByExampleSelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        method.getParameters().clear();

        FullyQualifiedJavaType parameterType = introspectedTable.getRules().calculateAllFieldsClass();
        method.addParameter(new Parameter(parameterType, "record", "@Param(\"record\")"));

        FullyQualifiedJavaType exampleType = new FullyQualifiedJavaType(introspectedTable.getExampleType());
        method.addParameter(new Parameter(exampleType, "example", "@Param(\"example\")"));

        // 找出全字段对应的Model
        FullyQualifiedJavaType fullFieldModel = introspectedTable.getRules().calculateAllFieldsClass();
        // column枚举
        FullyQualifiedJavaType selectiveType = new FullyQualifiedJavaType(fullFieldModel.getShortName() + "." + ModelColumnPlugin.ENUM_NAME);
        method.addParameter(new Parameter(selectiveType, "selective", "@Param(\"selective\")", true));

        FormatTools.replaceGeneralMethodComment(commentGenerator, method, introspectedTable);
        return super.clientUpdateByExampleSelectiveMethodGenerated(method, interfaze, introspectedTable);
    }

    /**
     * updateByPrimaryKeySelective
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param method
     * @param interfaze
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean clientUpdateByPrimaryKeySelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        method.getParameters().clear();

        FullyQualifiedJavaType parameterType;
        if (introspectedTable.getRules().generateRecordWithBLOBsClass()) {
            parameterType = new FullyQualifiedJavaType(introspectedTable.getRecordWithBLOBsType());
        } else {
            parameterType = new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
        }

        method.addParameter(new Parameter(parameterType, "record", "@Param(\"record\")"));

        // 找出全字段对应的Model
        FullyQualifiedJavaType fullFieldModel = introspectedTable.getRules().calculateAllFieldsClass();
        // column枚举
        FullyQualifiedJavaType selectiveType = new FullyQualifiedJavaType(fullFieldModel.getShortName() + "." + ModelColumnPlugin.ENUM_NAME);
        method.addParameter(new Parameter(selectiveType, "selective", "@Param(\"selective\")", true));

        FormatTools.replaceGeneralMethodComment(commentGenerator, method, introspectedTable);

        return super.clientUpdateByPrimaryKeySelectiveMethodGenerated(method, interfaze, introspectedTable);
    }

    /**
     * insertSelective
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapInsertSelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        // 清空
        XmlElement answer = new XmlElement("insert");
        answer.addAttribute(new Attribute("id", introspectedTable.getInsertSelectiveStatementId()));
        answer.addAttribute(new Attribute("parameterType", "map"));

        commentGenerator.addComment(answer);

        GeneratedKey gk = introspectedTable.getGeneratedKey();
        if (gk != null) {
            IntrospectedColumn introspectedColumn = introspectedTable.getColumn(gk.getColumn());
            // if the column is null, then it's a configuration error. The
            // warning has already been reported
            if (introspectedColumn != null) {
                if (gk.isJdbcStandard()) {
                    XmlElementGeneratorTools.useGeneratedKeys(answer, introspectedTable, "record.");
                } else {
                    answer.addElement(XmlElementGeneratorTools.getSelectKey(introspectedColumn, gk, "record."));
                }
            }
        }

        StringBuilder sb = new StringBuilder();

        sb.append("insert into ");
        sb.append(introspectedTable.getFullyQualifiedTableNameAtRuntime());
        answer.addElement(new TextElement(sb.toString()));

        // columns
        answer.addElement(this.generateInsertColumnSelective(ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns())));
        // values
        answer.addElement(new TextElement("values"));
        answer.addElement(this.generateInsertValuesSelective(ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns())));

        XmlElementTools.replaceXmlElement(element, answer);

        return super.sqlMapInsertSelectiveElementGenerated(element, introspectedTable);
    }

    /**
     * updateByExampleSelective
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpdateByExampleSelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        // 清空
        XmlElement answer = new XmlElement("update");
        answer.addAttribute(new Attribute("id", introspectedTable.getUpdateByExampleSelectiveStatementId()));
        answer.addAttribute(new Attribute("parameterType", "map"));

        commentGenerator.addComment(answer);

        StringBuilder sb = new StringBuilder();
        sb.append("update ");
        sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
        answer.addElement(new TextElement(sb.toString()));

        // selective
        answer.addElement(new TextElement("SET"));
        answer.addElement(this.generateSetsSelective(ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getAllColumns())));

        answer.addElement(XmlElementGeneratorTools.getUpdateByExampleIncludeElement(introspectedTable));

        XmlElementTools.replaceXmlElement(element, answer);

        return super.sqlMapUpdateByExampleSelectiveElementGenerated(element, introspectedTable);
    }

    /**
     * updateByPrimaryKeySelective
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param element
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpdateByPrimaryKeySelectiveElementGenerated(XmlElement element, IntrospectedTable introspectedTable) {
        // 清空
        XmlElement answer = new XmlElement("update");
        answer.addAttribute(new Attribute("id", introspectedTable.getUpdateByPrimaryKeySelectiveStatementId()));
        answer.addAttribute(new Attribute("parameterType", "map"));

        commentGenerator.addComment(answer);

        StringBuilder sb = new StringBuilder();

        sb.append("update ");
        sb.append(introspectedTable.getFullyQualifiedTableNameAtRuntime());
        answer.addElement(new TextElement(sb.toString()));

        // selective
        answer.addElement(new TextElement("SET"));
        answer.addElement(this.generateSetsSelective(ListUtilities.removeGeneratedAlwaysColumns(introspectedTable.getNonPrimaryKeyColumns())));

        XmlElementGeneratorTools.generateWhereByPrimaryKeyTo(answer, introspectedTable.getPrimaryKeyColumns(), "record.");

        XmlElementTools.replaceXmlElement(element, answer);
        return super.sqlMapUpdateByPrimaryKeySelectiveElementGenerated(element, introspectedTable);
    }

    // =============================================== IUpsertPluginHook ===================================================

    /**
     * upsertSelective 方法
     * @param method
     * @param interfaze
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean clientUpsertSelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        // @Param("record")
        method.getParameters().get(0).addAnnotation("@Param(\"record\")");
        // column枚举
        // 找出全字段对应的Model
        FullyQualifiedJavaType fullFieldModel = introspectedTable.getRules().calculateAllFieldsClass();
        FullyQualifiedJavaType selectiveType = new FullyQualifiedJavaType(fullFieldModel.getShortName() + "." + ModelColumnPlugin.ENUM_NAME);
        method.addParameter(new Parameter(selectiveType, "selective", "@Param(\"selective\")", true));
        return true;
    }

    /**
     * upsertByExampleSelective 方法
     * @param method
     * @param interfaze
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean clientUpsertByExampleSelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        // column枚举
        // 找出全字段对应的Model
        FullyQualifiedJavaType fullFieldModel = introspectedTable.getRules().calculateAllFieldsClass();
        FullyQualifiedJavaType selectiveType = new FullyQualifiedJavaType(fullFieldModel.getShortName() + "." + ModelColumnPlugin.ENUM_NAME);
        method.addParameter(new Parameter(selectiveType, "selective", "@Param(\"selective\")", true));
        return true;
    }

    /**
     * upsertSelective xml
     * @param element
     * @param columns
     * @param insertColumnsEle
     * @param insertValuesEle
     * @param setsEle
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpsertSelectiveElementGenerated(XmlElement element, List<IntrospectedColumn> columns, XmlElement insertColumnsEle, XmlElement insertValuesEle, XmlElement setsEle, IntrospectedTable introspectedTable) {
        // parameterType
        XmlElementTools.replaceAttribute(element, new Attribute("parameterType", "map"));
        // mybatis 3.5.0 之后对keyProperty处理有变更
        element.getAttributes().removeIf(attribute -> {
            String name = attribute.getName();
            return name.equals("useGeneratedKeys") || name.equals("keyProperty") || name.equals("keyColumn");
        });
        XmlElementGeneratorTools.useGeneratedKeys(element, introspectedTable, "record.");

        // 替换insert column
        XmlElementTools.replaceXmlElement(insertColumnsEle, this.generateInsertColumnSelective(columns));

        // 替换insert values
        XmlElementTools.replaceXmlElement(insertValuesEle, this.generateInsertValuesSelective(columns));

        // 替换update set
        XmlElementTools.replaceXmlElement(setsEle, this.generateSetsSelective(columns));

        return true;
    }

    /**
     * upsertByExampleSelective xml
     * @param element
     * @param columns
     * @param insertColumnsEle
     * @param insertValuesEle
     * @param setsEle
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean sqlMapUpsertByExampleSelectiveElementGenerated(XmlElement element, List<IntrospectedColumn> columns, XmlElement insertColumnsEle, XmlElement insertValuesEle, XmlElement setsEle, IntrospectedTable introspectedTable) {

        // 替换insert column
        XmlElementTools.replaceXmlElement(insertColumnsEle, this.generateInsertColumnSelective(columns));

        // 替换insert values
        XmlElementTools.replaceXmlElement(insertValuesEle, this.generateInsertValuesSelective(columns, false));

        // 替换update set
        XmlElementTools.replaceXmlElement(setsEle, this.generateSetsSelective(columns));

        return true;
    }

    // ================================================= IOptimisticLockerPluginHook ===============================================

    @Override
    public boolean clientUpdateWithVersionByExampleSelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        // issue#69 OptimisticLockerPlugin 插件updateWithVersionByExampleSelective方法的生成是基于updateByExampleSelective的，
        // 这个方法在配置了SelectiveEnhancedPlugin时可能已经被先配置的SelectiveEnhancedPlugin改变了
        if (!"selective".equals(method.getParameters().get(method.getParameters().size() - 1).getName())) {
            // column枚举,找出全字段对应的Model
            FullyQualifiedJavaType fullFieldModel = introspectedTable.getRules().calculateAllFieldsClass();
            FullyQualifiedJavaType selectiveType = new FullyQualifiedJavaType(fullFieldModel.getShortName() + "." + ModelColumnPlugin.ENUM_NAME);
            method.addParameter(new Parameter(selectiveType, "selective", "@Param(\"selective\")", true));
        }

        return true;
    }

    @Override
    public boolean clientUpdateWithVersionByPrimaryKeySelectiveMethodGenerated(Method method, Interface interfaze, IntrospectedTable introspectedTable) {
        // issue#69 OptimisticLockerPlugin 插件updateWithVersionByExampleSelective方法的生成是基于updateByExampleSelective的，
        // 这个方法在配置了SelectiveEnhancedPlugin时可能已经被先配置的SelectiveEnhancedPlugin改变了
        if (!"selective".equals(method.getParameters().get(method.getParameters().size() - 1).getName())) {
            // column枚举,找出全字段对应的Model
            FullyQualifiedJavaType fullFieldModel = introspectedTable.getRules().calculateAllFieldsClass();
            FullyQualifiedJavaType selectiveType = new FullyQualifiedJavaType(fullFieldModel.getShortName() + "." + ModelColumnPlugin.ENUM_NAME);
            method.addParameter(new Parameter(selectiveType, "selective", "@Param(\"selective\")", true));
        }
        return true;
    }

    @Override
    public boolean generateSetsSelectiveElement(List<IntrospectedColumn> columns, IntrospectedColumn versionColumn, XmlElement setsElement) {
        // 替换update set
        XmlElementTools.replaceXmlElement(setsElement, this.generateSetsSelective(columns, versionColumn));
        return true;
    }

    // ====================================================== 一些私有节点生成方法 =========================================================

    /**
     * insert column selective
     * @param columns
     * @return
     */
    private XmlElement generateInsertColumnSelective(List<IntrospectedColumn> columns) {
        XmlElement insertColumnsChooseEle = new XmlElement("choose");

        XmlElement insertWhenEle = new XmlElement("when");
        insertWhenEle.addAttribute(new Attribute("test", "selective != null and selective.length > 0"));
        insertColumnsChooseEle.addElement(insertWhenEle);

        XmlElement insertForeachEle = new XmlElement("foreach");
        insertForeachEle.addAttribute(new Attribute("collection", "selective"));
        insertForeachEle.addAttribute(new Attribute("item", "column"));
        insertForeachEle.addAttribute(new Attribute("open", "("));
        insertForeachEle.addAttribute(new Attribute("separator", ","));
        insertForeachEle.addAttribute(new Attribute("close", ")"));
        insertForeachEle.addElement(new TextElement("${column.escapedColumnName}"));
        insertWhenEle.addElement(insertForeachEle);

        XmlElement insertOtherwiseEle = new XmlElement("otherwise");
        insertOtherwiseEle.addElement(XmlElementGeneratorTools.generateKeysSelective(columns, "record."));
        insertColumnsChooseEle.addElement(insertOtherwiseEle);

        XmlElement insertTrimElement = new XmlElement("trim");
        insertTrimElement.addAttribute(new Attribute("prefix", "("));
        insertTrimElement.addAttribute(new Attribute("suffix", ")"));
        insertTrimElement.addAttribute(new Attribute("suffixOverrides", ","));
        insertOtherwiseEle.addElement(insertTrimElement);

        return insertColumnsChooseEle;
    }

    /**
     * insert column selective
     * @param columns
     * @return
     */
    private XmlElement generateInsertValuesSelective(List<IntrospectedColumn> columns) {
        return generateInsertValuesSelective(columns, true);
    }

    /**
     * insert column selective
     * @param columns
     * @param bracket
     * @return
     */
    private XmlElement generateInsertValuesSelective(List<IntrospectedColumn> columns, boolean bracket) {
        XmlElement insertValuesChooseEle = new XmlElement("choose");

        XmlElement valuesWhenEle = new XmlElement("when");
        valuesWhenEle.addAttribute(new Attribute("test", "selective != null and selective.length > 0"));
        insertValuesChooseEle.addElement(valuesWhenEle);

        XmlElement valuesForeachEle = new XmlElement("foreach");
        valuesForeachEle.addAttribute(new Attribute("collection", "selective"));
        valuesForeachEle.addAttribute(new Attribute("item", "column"));
        valuesForeachEle.addAttribute(new Attribute("separator", ","));
        if (bracket) {
            valuesForeachEle.addAttribute(new Attribute("open", "("));
            valuesForeachEle.addAttribute(new Attribute("close", ")"));
        }
        valuesForeachEle.addElement(new TextElement("#{record.${column.javaProperty},jdbcType=${column.jdbcType}}"));
        valuesWhenEle.addElement(valuesForeachEle);

        XmlElement valuesOtherwiseEle = new XmlElement("otherwise");
        insertValuesChooseEle.addElement(valuesOtherwiseEle);
        valuesOtherwiseEle.addElement(XmlElementGeneratorTools.generateValuesSelective(columns, "record.", bracket));

        return insertValuesChooseEle;
    }

    /**
     * sets selective
     * @param columns
     * @return
     */
    private XmlElement generateSetsSelective(List<IntrospectedColumn> columns) {
        return generateSetsSelective(columns, null);
    }

    /**
     * sets selective
     * @param columns
     * @return
     */
    private XmlElement generateSetsSelective(List<IntrospectedColumn> columns, IntrospectedColumn versionColumn) {
        XmlElement setsChooseEle = new XmlElement("choose");

        XmlElement setWhenEle = new XmlElement("when");
        setWhenEle.addAttribute(new Attribute("test", "selective != null and selective.length > 0"));
        setsChooseEle.addElement(setWhenEle);

        XmlElement setForeachEle = new XmlElement("foreach");
        setWhenEle.addElement(setForeachEle);
        setForeachEle.addAttribute(new Attribute("collection", "selective"));
        setForeachEle.addAttribute(new Attribute("item", "column"));
        setForeachEle.addAttribute(new Attribute("separator", ","));

        Element incrementEle = PluginTools.getHook(IIncrementsPluginHook.class).incrementSetsWithSelectiveEnhancedPluginElementGenerated(versionColumn);
        // 普通情况
        if (incrementEle == null && versionColumn == null) {
            setForeachEle.addElement(new TextElement("${column.escapedColumnName} = #{record.${column.javaProperty},jdbcType=${column.jdbcType}}"));
        } else if (incrementEle != null) {
            setForeachEle.addElement(incrementEle);
        } else if (versionColumn != null) {
            XmlElement ifEle = new XmlElement("if");
            ifEle.addAttribute(new Attribute("test", "column.value != '" + versionColumn.getActualColumnName() + "'.toString()"));
            ifEle.addElement(new TextElement("${column.escapedColumnName} = #{record.${column.javaProperty},jdbcType=${column.jdbcType}}"));

            setForeachEle.addElement(ifEle);
        }

        XmlElement setOtherwiseEle = new XmlElement("otherwise");
        setOtherwiseEle.addElement(XmlElementGeneratorTools.generateSetsSelective(columns, "record."));
        setsChooseEle.addElement(setOtherwiseEle);

        return setsChooseEle;
    }
}


/*
 * 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.ILogicalDeletePluginHook;
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.Document;
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.*;

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

/**
 * ---------------------------------------------------------------------------
 * 逻辑删除插件
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/1/13 14:08
 * ---------------------------------------------------------------------------
 */
public class LogicalDeletePlugin extends BasePlugin {
    public static final String METHOD_LOGICAL_DELETE_BY_EXAMPLE = "logicalDeleteByExample";
    public static final String METHOD_LOGICAL_DELETE_BY_PRIMARY_KEY = "logicalDeleteByPrimaryKey";

    public static final String PRO_LOGICAL_DELETE_COLUMN = "logicalDeleteColumn";
    public static final String PRO_LOGICAL_DELETE_VALUE = "logicalDeleteValue";
    public static final String PRO_LOGICAL_UN_DELETE_VALUE = "logicalUnDeleteValue";

    /**
     * 是否支持不推荐的常量配置方式
     */
    public static final String PRO_ENABLE_LOGICAL_DELETE_CONST = "enableLogicalDeleteConst";
    /**
     * 逻辑删除常量
     */
    public static final String PRO_LOGICAL_DELETE_CONST_NAME = "logicalDeleteConstName";
    public static final String PRO_LOGICAL_UN_DELETE_CONST_NAME = "logicalUnDeleteConstName";

    /**
     * 逻辑删除标志位名称(默认)
     */
    public static final String DEFAULT_LOGICAL_DELETE_NAME = "IS_DELETED";
    public static final String DEFAULT_LOGICAL_UN_DELETE_NAME = "NOT_DELETED";

    /**
     * 逻辑删除查询方法
     */
    public static final String METHOD_LOGICAL_DELETED = "andLogicalDeleted";
    /**
     * 增强selectByPrimaryKey是参数名称
     */
    public static final String PARAMETER_LOGICAL_DELETED = METHOD_LOGICAL_DELETED;
    /**
     * selectByPrimaryKey 的逻辑删除增强
     */
    public static final String METHOD_SELECT_BY_PRIMARY_KEY_WITH_LOGICAL_DELETE = "selectByPrimaryKeyWithLogicalDelete";

    /**
     * 逻辑删除列
     */
    private IntrospectedColumn logicalDeleteColumn;
    /**
     * 逻辑删除值
     */
    private String logicalDeleteValue;
    /**
     * 逻辑删除值（未删除）
     */
    private String logicalUnDeleteValue;
    /**
     * 逻辑删除常量
     */
    private String logicalDeleteConstName = DEFAULT_LOGICAL_DELETE_NAME;
    /**
     * 逻辑删除常量（未删除）
     */
    private String logicalUnDeleteConstName = DEFAULT_LOGICAL_UN_DELETE_NAME;
    /**
     * 是否支持常量类型
     */
    private Boolean enableLogicalDeleteConst;
    /**
     * 逻辑删除枚举
     */
    private InnerEnum logicalDeleteEnum;
    private int logicalUnDeleteEnumIndex = 0;
    private int logicalDeleteEnumIndex = 1;

    /**
     * 初始化阶段
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param introspectedTable
     * @return
     */
    @Override
    public void initialized(IntrospectedTable introspectedTable) {
        super.initialized(introspectedTable);
        this.logicalUnDeleteEnumIndex = 0;
        this.logicalDeleteEnumIndex = 1;

        // 1. 获取配置的逻辑删除列
        Properties properties = getProperties();
        String logicalDeleteColumn = properties.getProperty(PRO_LOGICAL_DELETE_COLUMN);
        if (introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_DELETE_COLUMN) != null) {
            logicalDeleteColumn = introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_DELETE_COLUMN);
        }
        this.logicalDeleteColumn = IntrospectedTableTools.safeGetColumn(introspectedTable, logicalDeleteColumn);
        // 判断如果表单独配置了逻辑删除列，但是却没有找到对应列进行提示
        if (introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_DELETE_COLUMN) != null && this.logicalDeleteColumn == null) {
            warnings.add("itfsw(逻辑删除插件):" + introspectedTable.getFullyQualifiedTable() + "没有找到您配置的逻辑删除列(" + introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_DELETE_COLUMN) + ")！");
        }

        // 2. 获取逻辑删除常量值
        this.enableLogicalDeleteConst = properties.getProperty(PRO_ENABLE_LOGICAL_DELETE_CONST) == null ? true : StringUtility.isTrue(properties.getProperty(PRO_ENABLE_LOGICAL_DELETE_CONST));
        if (this.enableLogicalDeleteConst) {
            if (properties.getProperty(PRO_LOGICAL_DELETE_CONST_NAME) != null) {
                this.logicalDeleteConstName = properties.getProperty(PRO_LOGICAL_DELETE_CONST_NAME).toUpperCase();
            }
            if (properties.getProperty(PRO_LOGICAL_UN_DELETE_CONST_NAME) != null) {
                this.logicalUnDeleteConstName = properties.getProperty(PRO_LOGICAL_UN_DELETE_CONST_NAME).toUpperCase();
            }
        }

        // 3.判断逻辑删除值是否配置了
        this.logicalDeleteValue = properties.getProperty(PRO_LOGICAL_DELETE_VALUE);
        this.logicalUnDeleteValue = properties.getProperty(PRO_LOGICAL_UN_DELETE_VALUE);
        if (introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_DELETE_VALUE) != null) {
            this.logicalDeleteValue = introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_DELETE_VALUE);
        }
        if (introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_UN_DELETE_VALUE) != null) {
            this.logicalUnDeleteValue = introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_UN_DELETE_VALUE);
        }
        if (this.logicalDeleteValue == null || this.logicalUnDeleteValue == null) {
            this.logicalDeleteColumn = null;
            warnings.add("itfsw(逻辑删除插件):" + introspectedTable.getFullyQualifiedTable() + "没有找到您配置的逻辑删除值，请全局或者局部配置logicalDeleteValue和logicalUnDeleteValue值！");
        }

        // 4. 优先借助 EnumTypeStatusPlugin 插件，去注解里面解析枚举
        if (this.logicalDeleteColumn != null) {
            EnumTypeStatusPlugin.EnumInfo enumInfo = null;
            try {
                enumInfo = new EnumTypeStatusPlugin.EnumInfo(this.logicalDeleteColumn);
                // 解析注释
                enumInfo.parseRemarks(this.logicalDeleteColumn.getRemarks());
            } catch (EnumTypeStatusPlugin.EnumInfo.NotSupportTypeException e) {
                this.logicalDeleteColumn = null;
                warnings.add("itfsw(逻辑删除插件):" + introspectedTable.getFullyQualifiedTable() + "逻辑删除列(" + introspectedTable.getTableConfigurationProperty(PRO_LOGICAL_DELETE_COLUMN) + ")的类型不在支持范围（请使用数字列，字符串列，布尔列）！");
            } catch (EnumTypeStatusPlugin.EnumInfo.CannotParseException e) {
                // 这个异常不管，没有配置是正常的
            } finally {

                if (enumInfo != null) {
                    // 这个是注释里配置了枚举
                    if (enumInfo.hasItems()) {
                        if (enumInfo.getItems().size() < 2) {
                            this.logicalDeleteColumn = null;
                            warnings.add("itfsw(逻辑删除插件):" + introspectedTable.getFullyQualifiedTable() + "在配合EnumTypeStatusPlugin插件使用时，枚举数量必须大于等于2！");
                        } else {
                            this.logicalDeleteEnumIndex = -1;
                            this.logicalUnDeleteEnumIndex = -1;
                            for (int i = 0; i < enumInfo.getItems().size(); i++) {
                                EnumTypeStatusPlugin.EnumInfo.EnumItemInfo enumItemInfo = enumInfo.getItems().get(i);
                                if (this.logicalDeleteValue.equals(enumItemInfo.getOriginalValue())) {
                                    this.logicalDeleteEnumIndex = i;
                                } else if (this.logicalUnDeleteValue.equals(enumItemInfo.getOriginalValue())) {
                                    this.logicalUnDeleteEnumIndex = i;
                                }
                            }
                            if (this.logicalDeleteEnumIndex == -1) {
                                this.logicalDeleteColumn = null;
                                warnings.add("itfsw(逻辑删除插件):" + introspectedTable.getFullyQualifiedTable() + "在配合EnumTypeStatusPlugin插件使用时，逻辑删除值“" + this.logicalDeleteValue + "”未在枚举中找到！");
                            } else if (this.logicalDeleteEnumIndex == -1) {
                                this.logicalDeleteColumn = null;
                                warnings.add("itfsw(逻辑删除插件):" + introspectedTable.getFullyQualifiedTable() + "在配合EnumTypeStatusPlugin插件使用时，逻辑未删除值“" + this.logicalUnDeleteValue + "”未在枚举中找到！");
                            } else {
                                this.logicalDeleteEnum = enumInfo.generateEnum(commentGenerator, introspectedTable);
                            }

                        }
                    } else {
                        // 兼容处理以前一些老用户配置的Long 和 Float配置问题
                        if (this.logicalDeleteColumn.getFullyQualifiedJavaType().getFullyQualifiedName().equals(Long.class.getName())) {
                            this.logicalUnDeleteValue = this.logicalUnDeleteValue.replaceAll("L|l", "");
                            this.logicalDeleteValue = this.logicalDeleteValue.replaceAll("L|l", "");
                        } else if (this.logicalDeleteColumn.getFullyQualifiedJavaType().getFullyQualifiedName().equals(Float.class.getName())) {
                            this.logicalUnDeleteValue = this.logicalUnDeleteValue.replaceAll("F|f", "");
                            this.logicalDeleteValue = this.logicalDeleteValue.replaceAll("F|f", "");
                        }

                        enumInfo.addItem(this.logicalUnDeleteConstName, "未删除", this.logicalUnDeleteValue);
                        enumInfo.addItem(this.logicalDeleteConstName, "已删除", this.logicalDeleteValue);
                        this.logicalDeleteEnum = enumInfo.generateEnum(commentGenerator, introspectedTable);
                    }
                }
            }
        }

        // 5. 防止增强的selectByPrimaryKey中逻辑删除键冲突
        if (this.logicalDeleteColumn != null) {
            Field logicalDeleteField = JavaBeansUtil.getJavaBeansField(this.logicalDeleteColumn, context, introspectedTable);
            if (logicalDeleteField.getName().equals(PARAMETER_LOGICAL_DELETED)) {
                this.logicalDeleteColumn = null;
                warnings.add("itfsw(逻辑删除插件):" + introspectedTable.getFullyQualifiedTable() + "配置的逻辑删除列和插件保留关键字(" + PARAMETER_LOGICAL_DELETED + ")冲突！");
            }
        }
    }

    /**
     * Java Client Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param interfaze
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        if (this.logicalDeleteColumn != null) {
            // 1. 逻辑删除ByExample
            Method mLogicalDeleteByExample = JavaElementGeneratorTools.generateMethod(
                    METHOD_LOGICAL_DELETE_BY_EXAMPLE,
                    JavaVisibility.DEFAULT,
                    FullyQualifiedJavaType.getIntInstance(),
                    new Parameter(new FullyQualifiedJavaType(introspectedTable.getExampleType()), "example", "@Param(\"example\")")
            );

            // 添加方法说明
            commentGenerator.addGeneralMethodComment(mLogicalDeleteByExample, introspectedTable);
            // interface 增加方法
            FormatTools.addMethodWithBestPosition(interfaze, mLogicalDeleteByExample);
            logger.debug("itfsw(逻辑删除插件):" + interfaze.getType().getShortName() + "增加方法logicalDeleteByExample。");
            // hook
            PluginTools.getHook(ILogicalDeletePluginHook.class).clientLogicalDeleteByExampleMethodGenerated(mLogicalDeleteByExample, interfaze, introspectedTable);

            // 2. 判断是否有主键，生成主键删除方法
            if (introspectedTable.hasPrimaryKeyColumns()) {
                // 2.1. 逻辑删除ByExample
                Method mLogicalDeleteByPrimaryKey = JavaElementGeneratorTools.generateMethod(
                        METHOD_LOGICAL_DELETE_BY_PRIMARY_KEY,
                        JavaVisibility.DEFAULT,
                        FullyQualifiedJavaType.getIntInstance()
                );
                commentGenerator.addGeneralMethodComment(mLogicalDeleteByPrimaryKey, introspectedTable);

                // 2.2 增强selectByPrimaryKey
                Method mSelectByPrimaryKey = JavaElementGeneratorTools.generateMethod(
                        METHOD_SELECT_BY_PRIMARY_KEY_WITH_LOGICAL_DELETE,
                        JavaVisibility.DEFAULT,
                        introspectedTable.getRules().calculateAllFieldsClass()
                );

                // 添加参数
                Set<FullyQualifiedJavaType> importedTypes = new TreeSet<>();
                importedTypes.add(new FullyQualifiedJavaType("org.apache.ibatis.annotations.Param"));

                if (introspectedTable.getRules().generatePrimaryKeyClass()) {
                    FullyQualifiedJavaType type1 = new FullyQualifiedJavaType(introspectedTable.getPrimaryKeyType());
                    importedTypes.add(type1);
                    mLogicalDeleteByPrimaryKey.addParameter(new Parameter(type1, "key"));
                    mSelectByPrimaryKey.addParameter(new Parameter(type1, "key", "@Param(\"key\")"));
                } else {
                    // no primary key class - fields are in the base class
                    // if more than one PK field, then we need to annotate the
                    // parameters
                    // for MyBatis
                    List<IntrospectedColumn> introspectedColumns = introspectedTable.getPrimaryKeyColumns();
                    boolean annotate = introspectedColumns.size() > 1;
                    StringBuilder sb = new StringBuilder();
                    for (IntrospectedColumn introspectedColumn : introspectedColumns) {
                        FullyQualifiedJavaType type1 = introspectedColumn.getFullyQualifiedJavaType();
                        importedTypes.add(type1);
                        Parameter parameter = new Parameter(type1, introspectedColumn.getJavaProperty());
                        if (annotate) {
                            sb.setLength(0);
                            sb.append("@Param(\"");
                            sb.append(introspectedColumn.getJavaProperty());
                            sb.append("\")");
                            parameter.addAnnotation(sb.toString());
                        }
                        mLogicalDeleteByPrimaryKey.addParameter(parameter);

                        Parameter parameter1 = new Parameter(type1, introspectedColumn.getJavaProperty());
                        sb.setLength(0);
                        sb.append("@Param(\"");
                        sb.append(introspectedColumn.getJavaProperty());
                        sb.append("\")");
                        parameter1.addAnnotation(sb.toString());

                        mSelectByPrimaryKey.addParameter(parameter1);
                    }
                }

                // interface 增加方法
                interfaze.addImportedTypes(importedTypes);
                FormatTools.addMethodWithBestPosition(interfaze, mLogicalDeleteByPrimaryKey);
                logger.debug("itfsw(逻辑删除插件):" + interfaze.getType().getShortName() + "增加方法logicalDeleteByPrimaryKey。");
                // hook
                PluginTools.getHook(ILogicalDeletePluginHook.class).clientLogicalDeleteByPrimaryKeyMethodGenerated(mLogicalDeleteByPrimaryKey, interfaze, introspectedTable);

                // 增强selectByPrimaryKey
                mSelectByPrimaryKey.addParameter(new Parameter(
                        FullyQualifiedJavaType.getBooleanPrimitiveInstance(),
                        PARAMETER_LOGICAL_DELETED,
                        "@Param(\"" + PARAMETER_LOGICAL_DELETED + "\")"
                ));
                commentGenerator.addGeneralMethodComment(mSelectByPrimaryKey, introspectedTable);
                FormatTools.addMethodWithBestPosition(interfaze, mSelectByPrimaryKey);
            }
        }
        return true;
    }

    /**
     * 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 (this.logicalDeleteColumn != null) {
            // 1. 逻辑删除ByExample
            XmlElement logicalDeleteByExample = new XmlElement("update");
            logicalDeleteByExample.addAttribute(new Attribute("id", METHOD_LOGICAL_DELETE_BY_EXAMPLE));
            logicalDeleteByExample.addAttribute(new Attribute("parameterType", "map"));
            commentGenerator.addComment(logicalDeleteByExample);

            StringBuilder sb = new StringBuilder();
            sb.append("update ");
            sb.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
            sb.append(" set ");
            // 更新逻辑删除字段
            sb.append(this.logicalDeleteColumn.getActualColumnName());
            sb.append(" = ");
            sb.append(XmlElementGeneratorTools.generateLogicalDeleteColumnValue(this.logicalDeleteColumn, this.logicalDeleteValue));

            logicalDeleteByExample.addElement(new TextElement(sb.toString()));

            logicalDeleteByExample.addElement(XmlElementGeneratorTools.getUpdateByExampleIncludeElement(introspectedTable));
            document.getRootElement().addElement(logicalDeleteByExample);
            logger.debug("itfsw(逻辑删除插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加方法logicalDeleteByExample的实现。");
            // hook
            PluginTools.getHook(ILogicalDeletePluginHook.class).sqlMapLogicalDeleteByExampleElementGenerated(document, logicalDeleteByExample, this.logicalDeleteColumn, this.logicalDeleteValue, introspectedTable);

            // 2. 判断是否有主键，生成主键删除方法
            if (introspectedTable.hasPrimaryKeyColumns()) {
                XmlElement logicalDeleteByPrimaryKey = new XmlElement("update");
                logicalDeleteByPrimaryKey.addAttribute(new Attribute("id", METHOD_LOGICAL_DELETE_BY_PRIMARY_KEY));

                String parameterClass;
                if (introspectedTable.getRules().generatePrimaryKeyClass()) {
                    parameterClass = introspectedTable.getPrimaryKeyType();
                } else {
                    // PK fields are in the base class. If more than on PK
                    // field, then they are coming in a map.
                    if (introspectedTable.getPrimaryKeyColumns().size() > 1) {
                        parameterClass = "map";
                    } else {
                        parameterClass = introspectedTable.getPrimaryKeyColumns().get(0).getFullyQualifiedJavaType().toString();
                    }
                }
                logicalDeleteByPrimaryKey.addAttribute(new Attribute("parameterType", parameterClass));
                commentGenerator.addComment(logicalDeleteByPrimaryKey);

                StringBuilder sb1 = new StringBuilder();
                sb1.append("update ");
                sb1.append(introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime());
                sb1.append(" set ");
                // 更新逻辑删除字段
                sb1.append(this.logicalDeleteColumn.getActualColumnName());
                sb1.append(" = ");
                sb1.append(XmlElementGeneratorTools.generateLogicalDeleteColumnValue(this.logicalDeleteColumn, this.logicalDeleteValue));

                logicalDeleteByPrimaryKey.addElement(new TextElement(sb1.toString()));

                XmlElementGeneratorTools.generateWhereByPrimaryKeyTo(logicalDeleteByPrimaryKey, introspectedTable.getPrimaryKeyColumns());

                document.getRootElement().addElement(logicalDeleteByPrimaryKey);
                logger.debug("itfsw(逻辑删除插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加方法logicalDeleteByPrimaryKey的实现。");
                // hook
                PluginTools.getHook(ILogicalDeletePluginHook.class).sqlMapLogicalDeleteByPrimaryKeyElementGenerated(document, logicalDeleteByPrimaryKey, this.logicalDeleteColumn, this.logicalDeleteValue, introspectedTable);

                // 3. 增强selectByPrimaryKey
                XmlElement selectByPrimaryKey = new XmlElement("select");
                commentGenerator.addComment(selectByPrimaryKey);

                selectByPrimaryKey.addAttribute(new Attribute("id", METHOD_SELECT_BY_PRIMARY_KEY_WITH_LOGICAL_DELETE));
                if (introspectedTable.getRules().generateResultMapWithBLOBs()) {
                    selectByPrimaryKey.addAttribute(new Attribute("resultMap", introspectedTable.getResultMapWithBLOBsId()));
                } else {
                    selectByPrimaryKey.addAttribute(new Attribute("resultMap", introspectedTable.getBaseResultMapId()));
                }

                selectByPrimaryKey.addAttribute(new Attribute("parameterType", "map"));

                sb = new StringBuilder();
                sb.append("select ");

                if (stringHasValue(introspectedTable.getSelectByPrimaryKeyQueryId())) {
                    sb.append('\'');
                    sb.append(introspectedTable.getSelectByPrimaryKeyQueryId());
                    sb.append("' as QUERYID,");
                }
                selectByPrimaryKey.addElement(new TextElement(sb.toString()));
                selectByPrimaryKey.addElement(XmlElementGeneratorTools.getBaseColumnListElement(introspectedTable));
                if (introspectedTable.hasBLOBColumns()) {
                    selectByPrimaryKey.addElement(new TextElement(","));
                    selectByPrimaryKey.addElement(XmlElementGeneratorTools.getBlobColumnListElement(introspectedTable));
                }

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

                XmlElementGeneratorTools.generateWhereByPrimaryKeyTo(selectByPrimaryKey, introspectedTable.getPrimaryKeyColumns());

                // 逻辑删除的判断
                sb.setLength(0);
                sb.append("  and ");
                sb.append(MyBatis3FormattingUtilities.getAliasedEscapedColumnName(this.logicalDeleteColumn));
                sb.append(" = ");
                selectByPrimaryKey.addElement(new TextElement(sb.toString()));

                XmlElement chooseEle = new XmlElement("choose");
                XmlElement whenEle = new XmlElement("when");
                whenEle.addAttribute(new Attribute("test", PARAMETER_LOGICAL_DELETED));
                whenEle.addElement(new TextElement(XmlElementGeneratorTools.generateLogicalDeleteColumnValue(this.logicalDeleteColumn, this.logicalDeleteValue)));
                chooseEle.addElement(whenEle);
                XmlElement otherwiseEle = new XmlElement("otherwise");
                otherwiseEle.addElement(new TextElement(XmlElementGeneratorTools.generateLogicalDeleteColumnValue(this.logicalDeleteColumn, this.logicalUnDeleteValue)));
                chooseEle.addElement(otherwiseEle);
                selectByPrimaryKey.addElement(chooseEle);

                FormatTools.addElementWithBestPosition(document.getRootElement(), selectByPrimaryKey);
            }
        }
        return true;
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param field
     * @param topLevelClass
     * @param introspectedColumn
     * @param introspectedTable
     * @param modelClassType
     * @return
     */
    @Override
    public boolean modelFieldGenerated(Field field, TopLevelClass topLevelClass, IntrospectedColumn introspectedColumn, IntrospectedTable introspectedTable, ModelClassType modelClassType) {
        if (this.logicalDeleteColumn != null) {
            // 常量、枚举和逻辑删除方法跟随 逻辑删除列走
            if (this.logicalDeleteColumn.getJavaProperty().equals(field.getName())) {
                // 1. 添加枚举
                if (PluginTools.getHook(ILogicalDeletePluginHook.class).logicalDeleteEnumGenerated(this.logicalDeleteColumn) == false) {
                    topLevelClass.addInnerEnum(this.logicalDeleteEnum);
                }
                // 2. andLogicalDeleted 方法
                Method mAndLogicalDeleted = JavaElementGeneratorTools.generateMethod(
                        METHOD_LOGICAL_DELETED,
                        JavaVisibility.PUBLIC,
                        null,
                        new Parameter(FullyQualifiedJavaType.getBooleanPrimitiveInstance(), "deleted")
                );
                commentGenerator.addGeneralMethodComment(mAndLogicalDeleted, introspectedTable);
                Method logicalDeleteSetter = JavaBeansUtil.getJavaBeansSetter(this.logicalDeleteColumn, context, introspectedTable);
                mAndLogicalDeleted.addBodyLine(logicalDeleteSetter.getName() + "(deleted ? " + this.getEnumConstantValue(true) + " : " + this.getEnumConstantValue(false) + ");");
                FormatTools.addMethodWithBestPosition(topLevelClass, mAndLogicalDeleted);

                // 3. 添加逻辑删除常量
                if (this.enableLogicalDeleteConst) {
                    Field logicalUnDeleteConstField = JavaElementGeneratorTools.generateStaticFinalField(
                            this.logicalUnDeleteConstName,
                            this.logicalDeleteColumn.getFullyQualifiedJavaType(),
                            this.getEnumConstantValue(false)
                    );
                    commentGenerator.addFieldComment(logicalUnDeleteConstField, introspectedTable);
                    Field logicalDeleteConstField = JavaElementGeneratorTools.generateStaticFinalField(
                            this.logicalDeleteConstName,
                            this.logicalDeleteColumn.getFullyQualifiedJavaType(),
                            this.getEnumConstantValue(true)
                    );
                    commentGenerator.addFieldComment(logicalDeleteConstField, introspectedTable);

                    // 常量放在字段开头
                    ArrayList<Field> fields = (ArrayList<Field>) topLevelClass.getFields();
                    fields.add(0, logicalUnDeleteConstField);
                    fields.add(0, logicalDeleteConstField);
                }
            }
        }
        return super.modelFieldGenerated(field, topLevelClass, introspectedColumn, introspectedTable, modelClassType);
    }

    /**
     * ModelExample Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        if (this.logicalDeleteColumn != null) {
            // 引入 Model类
            topLevelClass.addImportedType(this.getColumnInModelType());

            List<InnerClass> innerClasses = topLevelClass.getInnerClasses();
            for (InnerClass innerClass : innerClasses) {
                if ("Criteria".equals(innerClass.getType().getShortName())) {
                    // 增加逻辑删除条件
                    Method method = new Method(METHOD_LOGICAL_DELETED);
                    commentGenerator.addGeneralMethodComment(method, introspectedTable);
                    method.setVisibility(JavaVisibility.PUBLIC);
                    method.setReturnType(innerClass.getType());
                    method.addParameter(new Parameter(FullyQualifiedJavaType.getBooleanPrimitiveInstance(), "deleted"));

                    StringBuffer sb = new StringBuffer();
                    sb.append("return deleted ? ");

                    String modelName = this.getColumnInModelType().getShortName();

                    // 调用EqualTo方法
                    StringBuilder equalToMethodName = new StringBuilder();
                    equalToMethodName.append(this.logicalDeleteColumn.getJavaProperty());
                    equalToMethodName.setCharAt(0, Character.toUpperCase(equalToMethodName.charAt(0)));
                    equalToMethodName.insert(0, "and");
                    equalToMethodName.append("EqualTo");
                    sb.append(equalToMethodName);
                    sb.append("(" + modelName + "." + this.getEnumConstantValue(true) + ")");

                    sb.append(" : ");

                    // 调用NotEqualTo 方法
                    StringBuilder notEqualToMethodName = new StringBuilder();
                    notEqualToMethodName.append(this.logicalDeleteColumn.getJavaProperty());
                    notEqualToMethodName.setCharAt(0, Character.toUpperCase(notEqualToMethodName.charAt(0)));
                    notEqualToMethodName.insert(0, "and");
                    notEqualToMethodName.append("NotEqualTo");
                    sb.append(notEqualToMethodName);
                    sb.append("(" + modelName + "." + this.getEnumConstantValue(true) + ")");

                    sb.append(";");

                    method.addBodyLine(sb.toString());

                    FormatTools.addMethodWithBestPosition(innerClass, method);
                }
            }
        }
        return super.modelExampleClassGenerated(topLevelClass, introspectedTable);
    }

    /**
     * 获取逻辑删除枚举
     * @param delete
     * @return
     */
    private String getEnumConstantValue(boolean delete) {
        if (this.logicalDeleteEnum != null) {
            String enumConstant = this.logicalDeleteEnum.getEnumConstants().get(delete ? this.logicalDeleteEnumIndex : this.logicalUnDeleteEnumIndex);
            enumConstant = enumConstant.split("\\(")[0];
            return this.logicalDeleteEnum.getType().getShortName() + "." + enumConstant + ".value()";
        }
        return null;
    }

    /**
     * 获取逻辑删除列所在model(modelExampleClassGenerated执行顺序在前面！！！！！和官网上不一样，没办法只有自己去找）
     * @return
     */
    private FullyQualifiedJavaType getColumnInModelType() {
        if (this.logicalDeleteColumn != null) {
            IntrospectedTable introspectedTable = this.logicalDeleteColumn.getIntrospectedTable();

            // primaryKey
            for (IntrospectedColumn column : introspectedTable.getPrimaryKeyColumns()) {
                if (column.getActualColumnName().equals(this.logicalDeleteColumn.getActualColumnName())) {
                    if (introspectedTable.getRules().generatePrimaryKeyClass()) {
                        return new FullyQualifiedJavaType(introspectedTable.getPrimaryKeyType());
                    } else {
                        return new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
                    }
                }
            }

            // base record
            for (IntrospectedColumn column : introspectedTable.getBaseColumns()) {
                if (column.getActualColumnName().equals(this.logicalDeleteColumn.getActualColumnName())) {
                    if (introspectedTable.getRules().generateBaseRecordClass()) {
                        return new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
                    }
                }
            }

            // blob record
            for (IntrospectedColumn column : introspectedTable.getBLOBColumns()) {
                if (column.getActualColumnName().equals(this.logicalDeleteColumn.getActualColumnName())) {
                    if (introspectedTable.getRules().generateRecordWithBLOBsClass()) {
                        return new FullyQualifiedJavaType(introspectedTable.getRecordWithBLOBsType());
                    } else {
                        return new FullyQualifiedJavaType(introspectedTable.getBaseRecordType());
                    }
                }
            }
        }
        return null;
    }
}