/*
 * 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 freemarker.template.Configuration;
import freemarker.template.Template;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.api.dom.java.*;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.config.MergeConstants;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.internal.util.StringUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.*;

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

/**
 * ---------------------------------------------------------------------------
 * 模板注释生成工具
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/6/8 13:21
 * ---------------------------------------------------------------------------
 */
public class TemplateCommentGenerator implements CommentGenerator {
    protected static final Logger logger = LoggerFactory.getLogger(TemplateCommentGenerator.class); // 日志

    private Map<EnumNode, Template> templates = new HashMap<>();  // 模板

    private boolean suppressDate = false;

    private boolean suppressAllComments = false;

    /**
     * 构造函数
     * @param templatePath 模板路径
     * @param useForDefault 未使用Comment插件，用作默认注释生成器
     */
    public TemplateCommentGenerator(String templatePath, boolean useForDefault) {
        try {
            Document doc = null;
            if (useForDefault){
                InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(templatePath);
                doc = new SAXReader().read(inputStream);
                inputStream.close();
            } else {
                File file = new File(templatePath);
                if (file.exists()) {
                    doc = new SAXReader().read(file);
                } else {
                    logger.error("没有找到对应注释模板:" + templatePath);
                }
            }

            // 遍历comment 节点
            if (doc != null){
                for (EnumNode node : EnumNode.values()){
                    Element element = doc.getRootElement().elementByID(node.value());
                    if (element != null){
                        Configuration cfg = new Configuration(Configuration.VERSION_2_3_26);
                        // 字符串清理
                        Template template = new Template(node.value(), element.getText(), cfg);
                        templates.put(node, template);
                    }
                }
            }
        } catch (Exception e) {
            logger.error("注释模板XML解析失败！", e);
        }
    }

    /**
     * 获取评论
     * @param map 模板参数
     * @param node  节点ID
     * @return
     */
    private String[] getComments(Map<String, Object> map, EnumNode node) {
        // 1. 模板引擎解析
        try {
            StringWriter stringWriter = new StringWriter();
            Template template = templates.get(node);
            if (template != null){
                template.process(map, stringWriter);

                String comment = stringWriter.toString();
                stringWriter.close();

                // 需要先清理字符串
                return comment.replaceFirst("^[\\s\\t\\r\\n]*", "").replaceFirst("[\\s\\t\\r\\n]*$", "").split("\n");
            }

        } catch (Exception e) {
            logger.error("freemarker 解析失败！", e);
        }

        return null;
    }

    /**
     * 添加评论
     *
     * @param javaElement
     * @param map
     * @param node
     */
    private void addJavaElementComment(JavaElement javaElement, Map<String, Object> map, EnumNode node) {
        // 获取评论
        String[] comments = getComments(map, node);
        if (comments != null){
            // 去除空评论
            if (comments.length == 1 && !StringUtility.stringHasValue(comments[0])){
                return;
            }
            // 添加评论
            for (String comment : comments){
                javaElement.addJavaDocLine(comment);
            }
        }
    }

    /**
     * 添加评论
     *
     * @param compilationUnit
     * @param map
     * @param node
     */
    private void addCompilationUnitComment(CompilationUnit compilationUnit, Map<String, Object> map, EnumNode node) {
        // 获取评论
        String[] comments = getComments(map, node);
        if (comments != null){
            // 去除空评论
            if (comments.length == 1 && !StringUtility.stringHasValue(comments[0])){
                return;
            }
            // 添加评论
            for (String comment : comments){
                compilationUnit.addFileCommentLine(comment);
            }
        }
    }

    /**
     * 添加评论
     *
     * @param xmlElement
     * @param map
     * @param node
     */
    private void addXmlElementComment(XmlElement xmlElement, Map<String, Object> map, EnumNode node) {
        // 获取评论
        String[] comments = getComments(map, node);
        if (comments != null){
            // 去除空评论
            if (comments.length == 1 && !StringUtility.stringHasValue(comments[0])){
                return;
            }
            // 添加评论
            for (String comment : comments){
                xmlElement.addElement(new TextElement(comment));
            }
        }
    }

    /**
     * Adds properties for this instance from any properties configured in the
     * CommentGenerator configuration.
     *
     * This method will be called before any of the other methods.
     *
     * @param properties
     *            All properties from the configuration
     */
    @Override
    public void addConfigurationProperties(Properties properties) {
        suppressDate = isTrue(properties
                .getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_DATE));

        suppressAllComments = isTrue(properties
                .getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_ALL_COMMENTS));
    }

    /**
     * This method should add a Javadoc comment to the specified field. The field is related to the specified table and
     * is used to hold the value of the specified column.
     *

     *
     * Important: This method should add a the nonstandard JavaDoc tag "@mbg.generated" to the comment. Without
     * this tag, the Eclipse based Java merge feature will fail.
     *
     * @param field
     *            the field
     * @param introspectedTable
     *            the introspected table
     * @param introspectedColumn
     *            the introspected column
     */
    @Override
    public void addFieldComment(Field field, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("field", field);
        map.put("introspectedTable", introspectedTable);
        map.put("introspectedColumn", introspectedColumn);

        // 添加评论
        addJavaElementComment(field, map, EnumNode.ADD_FIELD_COMMENT);
    }

    /**
     * Adds the field comment.
     *
     * @param field
     *            the field
     * @param introspectedTable
     *            the introspected table
     */
    @Override
   public void addFieldComment(Field field, IntrospectedTable introspectedTable) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("field", field);
        map.put("introspectedTable", introspectedTable);

        // 添加评论
        addJavaElementComment(field, map, EnumNode.ADD_FIELD_COMMENT);
    }

    /**
     * Adds a comment for a model class.  The Java code merger should
     * be notified not to delete the entire class in case any manual
     * changes have been made.  So this method will always use the
     * "do not delete" annotation.
     *
     * Because of difficulties with the Java file merger, the default implementation
     * of this method should NOT add comments.  Comments should only be added if
     * specifically requested by the user (for example, by enabling table remark comments).
     *
     * @param topLevelClass
     *            the top level class
     * @param introspectedTable
     *            the introspected table
     */
    @Override
    public void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("topLevelClass", topLevelClass);
        map.put("introspectedTable", introspectedTable);

        // 添加评论
        addJavaElementComment(topLevelClass, map, EnumNode.ADD_MODEL_CLASS_COMMENT);
    }

    /**
     * Adds the inner class comment.
     *
     * @param innerClass
     *            the inner class
     * @param introspectedTable
     *            the introspected table
     */
    @Override
    public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable) {
        if (innerClass instanceof InnerInterfaceWrapperToInnerClass){
            InnerInterface innerInterface = ((InnerInterfaceWrapperToInnerClass)innerClass).getInnerInterface();

            Map<String, Object> map = new HashMap<>();
            map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
            map.put("innerInterface", innerInterface);
            map.put("introspectedTable", introspectedTable);

            // 添加评论
            addJavaElementComment(innerInterface, map, EnumNode.ADD_INTERFACE_COMMENT);
        } else {
            Map<String, Object> map = new HashMap<>();
            map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
            map.put("innerClass", innerClass);
            map.put("introspectedTable", introspectedTable);

            // 添加评论
            addJavaElementComment(innerClass, map, EnumNode.ADD_CLASS_COMMENT);
        }
    }

    /**
     * Adds the inner class comment.
     *
     * @param innerClass
     *            the inner class
     * @param introspectedTable
     *            the introspected table
     * @param markAsDoNotDelete
     *            the mark as do not delete
     */
    @Override
    public void addClassComment(InnerClass innerClass, IntrospectedTable introspectedTable, boolean markAsDoNotDelete) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("innerClass", innerClass);
        map.put("introspectedTable", introspectedTable);
        map.put("markAsDoNotDelete", markAsDoNotDelete);

        // 添加评论
        addJavaElementComment(innerClass, map, EnumNode.ADD_CLASS_COMMENT);
    }

    /**
     * Adds the enum comment.
     *
     * @param innerEnum
     *            the inner enum
     * @param introspectedTable
     *            the introspected table
     */
    @Override
    public void addEnumComment(InnerEnum innerEnum, IntrospectedTable introspectedTable) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("innerEnum", innerEnum);
        map.put("introspectedTable", introspectedTable);

        // 添加评论
        addJavaElementComment(innerEnum, map, EnumNode.ADD_ENUM_COMMENT);
    }

    /**
     * Adds the getter comment.
     *
     * @param method
     *            the method
     * @param introspectedTable
     *            the introspected table
     * @param introspectedColumn
     *            the introspected column
     */
    @Override
    public void addGetterComment(Method method, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("method", method);
        map.put("introspectedTable", introspectedTable);
        map.put("introspectedColumn", introspectedColumn);

        // 添加评论
        addJavaElementComment(method, map, EnumNode.ADD_GETTER_COMMENT);
    }

    /**
     * Adds the setter comment.
     *
     * @param method
     *            the method
     * @param introspectedTable
     *            the introspected table
     * @param introspectedColumn
     *            the introspected column
     */
    @Override
    public void addSetterComment(Method method, IntrospectedTable introspectedTable, IntrospectedColumn introspectedColumn) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("method", method);
        map.put("introspectedTable", introspectedTable);
        map.put("introspectedColumn", introspectedColumn);

        // 添加评论
        addJavaElementComment(method, map, EnumNode.ADD_SETTER_COMMENT);
    }

    /**
     * Adds the general method comment.
     *
     * @param method
     *            the method
     * @param introspectedTable
     *            the introspected table
     */
    @Override
    public void addGeneralMethodComment(Method method, IntrospectedTable introspectedTable) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("method", method);
        map.put("introspectedTable", introspectedTable);

        // 添加评论
        addJavaElementComment(method, map, EnumNode.ADD_GENERAL_METHOD_COMMENT);
    }

    /**
     * This method is called to add a file level comment to a generated java file. This method could be used to add a
     * general file comment (such as a copyright notice). However, note that the Java file merge function in Eclipse
     * does not deal with this comment. If you run the generator repeatedly, you will only retain the comment from the
     * initial run.
     *

     *
     * The default implementation does nothing.
     *
     * @param compilationUnit
     *            the compilation unit
     */
    @Override
    public void addJavaFileComment(CompilationUnit compilationUnit) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("compilationUnit", compilationUnit);

        // 添加评论
        addCompilationUnitComment(compilationUnit, map, EnumNode.ADD_JAVA_FILE_COMMENT);
    }

    /**
     * This method should add a suitable comment as a child element of the specified xmlElement to warn users that the
     * element was generated and is subject to regeneration.
     *
     * @param xmlElement
     *            the xml element
     */
    @Override
    public void addComment(XmlElement xmlElement) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("xmlElement", xmlElement);

        // 添加评论
        addXmlElementComment(xmlElement, map, EnumNode.ADD_COMMENT);
    }

    /**
     * This method is called to add a comment as the first child of the root element. This method could be used to add a
     * general file comment (such as a copyright notice). However, note that the XML file merge function does not deal
     * with this comment. If you run the generator repeatedly, you will only retain the comment from the initial run.
     *

     *
     * The default implementation does nothing.
     *
     * @param rootElement
     *            the root element
     */
    @Override
    public void addRootComment(XmlElement rootElement) {
        Map<String, Object> map = new HashMap<>();
        map.put("mgb", MergeConstants.NEW_ELEMENT_TAG);
        map.put("rootElement", rootElement);

        // 添加评论
        addXmlElementComment(rootElement, map, EnumNode.ADD_ROOT_COMMENT);
    }

    @Override
    public void addGeneralMethodAnnotation(Method method, IntrospectedTable introspectedTable,
                                           Set<FullyQualifiedJavaType> imports) {
        imports.add(new FullyQualifiedJavaType("javax.annotation.Generated")); //$NON-NLS-1$
        String comment = "Source Table: " + introspectedTable.getFullyQualifiedTable().toString(); //$NON-NLS-1$
        method.addAnnotation(getGeneratedAnnotation(comment));
    }

    @Override
    public void addGeneralMethodAnnotation(Method method, IntrospectedTable introspectedTable,
                                           IntrospectedColumn introspectedColumn, Set<FullyQualifiedJavaType> imports) {
        imports.add(new FullyQualifiedJavaType("javax.annotation.Generated")); //$NON-NLS-1$
        String comment = "Source field: " //$NON-NLS-1$
                + introspectedTable.getFullyQualifiedTable().toString()
                + "." //$NON-NLS-1$
                + introspectedColumn.getActualColumnName();
        method.addAnnotation(getGeneratedAnnotation(comment));
    }

    @Override
    public void addFieldAnnotation(Field field, IntrospectedTable introspectedTable,
                                   Set<FullyQualifiedJavaType> imports) {
        imports.add(new FullyQualifiedJavaType("javax.annotation.Generated")); //$NON-NLS-1$
        String comment = "Source Table: " + introspectedTable.getFullyQualifiedTable().toString(); //$NON-NLS-1$
        field.addAnnotation(getGeneratedAnnotation(comment));
    }

    @Override
    public void addFieldAnnotation(Field field, IntrospectedTable introspectedTable,
                                   IntrospectedColumn introspectedColumn, Set<FullyQualifiedJavaType> imports) {
        imports.add(new FullyQualifiedJavaType("javax.annotation.Generated")); //$NON-NLS-1$
        String comment = "Source field: " //$NON-NLS-1$
                + introspectedTable.getFullyQualifiedTable().toString()
                + "." //$NON-NLS-1$
                + introspectedColumn.getActualColumnName();
        field.addAnnotation(getGeneratedAnnotation(comment));
    }

    @Override
    public void addClassAnnotation(InnerClass innerClass, IntrospectedTable introspectedTable,
                                   Set<FullyQualifiedJavaType> imports) {
        imports.add(new FullyQualifiedJavaType("javax.annotation.Generated")); //$NON-NLS-1$
        String comment = "Source Table: " + introspectedTable.getFullyQualifiedTable().toString(); //$NON-NLS-1$
        innerClass.addAnnotation(getGeneratedAnnotation(comment));
    }

    private String getGeneratedAnnotation(String comment) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("@Generated("); //$NON-NLS-1$
        if (suppressAllComments) {
            buffer.append('\"');
        } else {
            buffer.append("value=\""); //$NON-NLS-1$
        }

        buffer.append(MyBatisGenerator.class.getName());
        buffer.append('\"');

        if (!suppressDate && !suppressAllComments) {
            buffer.append(", date=\""); //$NON-NLS-1$
            buffer.append(DatatypeConverter.printDateTime(Calendar.getInstance()));
            buffer.append('\"');
        }

        if (!suppressAllComments) {
            buffer.append(", comments=\""); //$NON-NLS-1$
            buffer.append(comment);
            buffer.append('\"');
        }

        buffer.append(')');
        return buffer.toString();
    }

    /**
     * 评论模板节点ID
     */
    public static enum EnumNode {
        ADD_COMMENT("addComment"),  // Xml 节点注释
        ADD_ROOT_COMMENT("addRootComment"),   // xml root 节点注释
        ADD_JAVA_FILE_COMMENT("addJavaFileComment"),   // java 文件注释
        ADD_GENERAL_METHOD_COMMENT("addGeneralMethodComment"), // java 方法注释
        ADD_SETTER_COMMENT("addSetterComment"), // setter 方法注释
        ADD_GETTER_COMMENT("addGetterComment"), // getter 方式注释
        ADD_ENUM_COMMENT("addEnumComment"), // 枚举 注释
        ADD_CLASS_COMMENT("addClassComment"),   // 类 注释
        ADD_INTERFACE_COMMENT("addInterfaceComment"),   // 接口 注释
        ADD_MODEL_CLASS_COMMENT("addModelClassComment"),    // model 类注释
        ADD_FIELD_COMMENT("addFieldComment"),   // 字段 注释
        ;

        private final String value; // 值

        /**
         * 构造方法
         * @param value
         */
        EnumNode(String value) {
            this.value = value;
        }

        /**
         * 值
         *
         * @return
         */
        public String value(){
            return value;
        }
    }
}


/*
 * 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.CommentPlugin;
import com.itfsw.mybatis.generator.plugins.utils.enhanced.TemplateCommentGenerator;
import com.itfsw.mybatis.generator.plugins.utils.hook.HookAggregator;
import com.itfsw.mybatis.generator.plugins.utils.hook.ITableConfigurationHook;
import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.PluginConfiguration;
import org.mybatis.generator.internal.DefaultCommentGenerator;
import org.mybatis.generator.internal.util.StringUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * ---------------------------------------------------------------------------
 * 基础plugin
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/4/28 13:57
 * ---------------------------------------------------------------------------
 */
public class BasePlugin extends PluginAdapter {
    protected static final Logger logger = LoggerFactory.getLogger(BasePlugin.class);
    protected CommentGenerator commentGenerator;
    protected List<String> warnings;

    /**
     * mybatis 版本
     */
    public static final String PRO_MYBATIS_VERSION = "mybatisVersion";
    protected String mybatisVersion = "3.5.0";

    /**
     * Set the context under which this plugin is running.
     * @param context the new context
     */
    @Override
    public void setContext(Context context) {
        super.setContext(context);

        // 添加插件
        HookAggregator.getInstance().setContext(context);

        // 配置插件使用的模板引擎
        PluginConfiguration cfg = PluginTools.getPluginConfiguration(context, CommentPlugin.class);

        if (cfg == null || cfg.getProperty(CommentPlugin.PRO_TEMPLATE) == null) {
            if (context.getCommentGenerator() instanceof DefaultCommentGenerator) {
                // 使用默认模板引擎
                commentGenerator = new TemplateCommentGenerator("default-comment.ftl", true);
            } else {
                // 用户自定义
                commentGenerator = context.getCommentGenerator();
            }
        } else {
            TemplateCommentGenerator templateCommentGenerator = new TemplateCommentGenerator(cfg.getProperty(CommentPlugin.PRO_TEMPLATE), false);

            // ITFSW 插件使用的注释生成器
            commentGenerator = templateCommentGenerator;

            // 修正系统插件
            try {
                // 先执行一次生成CommentGenerator操作，然后再替换
                context.getCommentGenerator();

                BeanUtils.setProperty(context, "commentGenerator", templateCommentGenerator);
            } catch (Exception e) {
                logger.error("反射异常", e);
            }
        }

        // mybatis版本
        if (StringUtility.stringHasValue(context.getProperty(PRO_MYBATIS_VERSION))) {
            this.mybatisVersion = context.getProperty(PRO_MYBATIS_VERSION);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean validate(List<String> warnings) {
        this.warnings = warnings;
        // 插件使用前提是targetRuntime为MyBatis3
        if (StringUtility.stringHasValue(getContext().getTargetRuntime()) && "MyBatis3".equalsIgnoreCase(getContext().getTargetRuntime()) == false) {
            warnings.add("itfsw:插件" + this.getClass().getTypeName() + "要求运行targetRuntime必须为MyBatis3！");
            return false;
        }

        return true;
    }

    /**
     * {@inheritDoc}
     * @param introspectedTable
     */
    @Override
    public void initialized(IntrospectedTable introspectedTable) {
        super.initialized(introspectedTable);
        if (StringUtility.stringHasValue(introspectedTable.getTableConfiguration().getAlias())) {
            warnings.add("itfsw:插件" + this.getClass().getTypeName() + "请不要配置alias属性，这个属性官方支持也很混乱，导致插件支持会存在问题！");
        }
        PluginTools.getHook(ITableConfigurationHook.class).tableConfiguration(introspectedTable);

        // mybatis版本
        if (StringUtility.stringHasValue(this.getProperties().getProperty(PRO_MYBATIS_VERSION))) {
            this.mybatisVersion = this.getProperties().getProperty(PRO_MYBATIS_VERSION);
        }
    }
}


/*
 * 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.java.FullyQualifiedJavaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ---------------------------------------------------------------------------
 *
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/8/2 16:55
 * ---------------------------------------------------------------------------
 */
public class InnerTypeFullyQualifiedJavaType extends FullyQualifiedJavaType {
    private final static Logger logger = LoggerFactory.getLogger(InnerTypeFullyQualifiedJavaType.class);
    private String outerType;   // 内部类
    /**
     * Use this constructor to construct a generic type with the specified type parameters.
     * @param fullTypeSpecification the full type specification
     */
    public InnerTypeFullyQualifiedJavaType(String fullTypeSpecification){
        super(fullTypeSpecification);

        try{
            // 修正package
            java.lang.reflect.Field packageName = this.getClass().getSuperclass().getDeclaredField("packageName");
            packageName.setAccessible(true);
            String oldPackageName = getPackageName();
            packageName.set(this, oldPackageName.substring(0, oldPackageName.lastIndexOf(".")));

            outerType = oldPackageName.substring(oldPackageName.lastIndexOf(".") + 1);
        } catch (Exception e){
            logger.error("InnerTypeFullyQualifiedJavaType 赋值失败！", e);
        }
    }

    /**
     * This method returns the fully qualified name - including any generic type parameters.
     *
     * @return Returns the fullyQualifiedName.
     */
    @Override
    public String getFullyQualifiedName() {
        String fullyQualifiedName = super.getFullyQualifiedName();
        String before = fullyQualifiedName.substring(0, fullyQualifiedName.lastIndexOf("."));
        String end = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf("."));
        return before + "." + outerType + end;
    }

    /**
     * Gets the short name.
     *
     * @return Returns the shortName - including any type arguments.
     */
    @Override
    public String getShortName() {
        return outerType + "." + super.getShortName();
    }
}


/*
 * 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.utils.hook.IIncrementsPluginHook;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.dom.OutputUtilities;
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.config.GeneratedKey;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

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

/**
 * ---------------------------------------------------------------------------
 * Xml 节点生成工具 参考 org.mybatis.generator.codegen.mybatis3.xmlmapper.elements.AbstractXmlElementGenerator
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2016/12/29 16:47
 * ---------------------------------------------------------------------------
 */
public class XmlElementGeneratorTools {

    /**
     * This method should return an XmlElement for the select key used to
     * automatically generate keys.
     * @param introspectedColumn the column related to the select key statement
     * @param generatedKey       the generated key for the current table
     * @return the selectKey element
     */
    public static Element getSelectKey(IntrospectedColumn introspectedColumn, GeneratedKey generatedKey) {
        return getSelectKey(introspectedColumn, generatedKey, null);
    }

    public static Element getSelectKey(IntrospectedColumn introspectedColumn, GeneratedKey generatedKey, String prefix) {
        String identityColumnType = introspectedColumn.getFullyQualifiedJavaType().getFullyQualifiedName();

        XmlElement answer = new XmlElement("selectKey");
        answer.addAttribute(new Attribute("resultType", identityColumnType));
        answer.addAttribute(new Attribute("keyProperty", (prefix == null ? "" : prefix) + introspectedColumn.getJavaProperty()));
        answer.addAttribute(new Attribute("order", generatedKey.getMyBatis3Order()));

        answer.addElement(new TextElement(generatedKey.getRuntimeSqlStatement()));

        return answer;
    }

    public static Element getBaseColumnListElement(IntrospectedTable introspectedTable) {
        XmlElement answer = new XmlElement("include");
        answer.addAttribute(new Attribute("refid", introspectedTable.getBaseColumnListId()));
        return answer;
    }

    public static Element getBlobColumnListElement(IntrospectedTable introspectedTable) {
        XmlElement answer = new XmlElement("include");
        answer.addAttribute(new Attribute("refid", introspectedTable.getBlobColumnListId()));
        return answer;
    }

    public static Element getExampleIncludeElement(IntrospectedTable introspectedTable) {
        XmlElement ifElement = new XmlElement("if");
        ifElement.addAttribute(new Attribute("test", "_parameter != null"));

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

        return ifElement;
    }

    public static Element getUpdateByExampleIncludeElement(IntrospectedTable introspectedTable) {
        XmlElement ifElement = new XmlElement("if");
        ifElement.addAttribute(new Attribute("test", "_parameter != null"));

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

        return ifElement;
    }

    /**
     * 使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。所以只支持MYSQL和SQLServer
     * @param element
     * @param introspectedTable
     */
    public static void useGeneratedKeys(XmlElement element, IntrospectedTable introspectedTable) {
        useGeneratedKeys(element, introspectedTable, null);
    }

    /**
     * 使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。所以只支持MYSQL和SQLServer
     * @param element
     * @param introspectedTable
     * @param prefix
     */
    public static void useGeneratedKeys(XmlElement element, IntrospectedTable introspectedTable, String prefix) {
        GeneratedKey gk = introspectedTable.getGeneratedKey();
        if (gk != null) {
            IntrospectedColumn introspectedColumn = IntrospectedTableTools.safeGetColumn(introspectedTable, gk.getColumn());
            // if the column is null, then it's a configuration error. The
            // warning has already been reported
            if (introspectedColumn != null) {
                // 使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。所以只支持MYSQL和SQLServer
                element.addAttribute(new Attribute("useGeneratedKeys", "true"));
                element.addAttribute(new Attribute("keyProperty", (prefix == null ? "" : prefix) + introspectedColumn.getJavaProperty()));
                element.addAttribute(new Attribute("keyColumn", introspectedColumn.getActualColumnName()));
            }
        }
    }

    /**
     * 生成keys Ele
     * @param columns
     * @return
     */
    public static List<Element> generateKeys(List<IntrospectedColumn> columns) {
        return generateKeys(columns, false);
    }

    /**
     * 生成keys Ele
     * @param columns
     * @param bracket
     * @return
     */
    public static List<Element> generateKeys(List<IntrospectedColumn> columns, boolean bracket) {
        return generateCommColumns(columns, null, bracket, 1);
    }

    /**
     * 生成keys Selective Ele
     * @param columns
     * @return
     */
    public static XmlElement generateKeysSelective(List<IntrospectedColumn> columns) {
        return generateKeysSelective(columns, null);
    }

    /**
     * 生成keys Selective Ele
     * @param columns
     * @param prefix
     * @return
     */
    public static XmlElement generateKeysSelective(List<IntrospectedColumn> columns, String prefix) {
        return generateKeysSelective(columns, prefix, true);
    }

    /**
     * 生成keys Selective Ele
     * @param columns
     * @param prefix
     * @param bracket
     * @return
     */
    public static XmlElement generateKeysSelective(List<IntrospectedColumn> columns, String prefix, boolean bracket) {
        return generateCommColumnsSelective(columns, prefix, bracket, 1);
    }

    /**
     * 生成values Ele
     * @param columns
     * @return
     */
    public static List<Element> generateValues(List<IntrospectedColumn> columns) {
        return generateValues(columns, null);
    }

    /**
     * 生成values Ele
     * @param columns
     * @param prefix
     * @return
     */
    public static List<Element> generateValues(List<IntrospectedColumn> columns, String prefix) {
        return generateValues(columns, prefix, true);
    }

    /**
     * 生成values Ele
     * @param columns
     * @param prefix
     * @param bracket
     * @return
     */
    public static List<Element> generateValues(List<IntrospectedColumn> columns, String prefix, boolean bracket) {
        return generateCommColumns(columns, prefix, bracket, 2);
    }

    /**
     * 生成values Selective Ele
     * @param columns
     * @return
     */
    public static XmlElement generateValuesSelective(List<IntrospectedColumn> columns) {
        return generateValuesSelective(columns, null);
    }

    /**
     * 生成values Selective Ele
     * @param columns
     * @param prefix
     * @return
     */
    public static XmlElement generateValuesSelective(List<IntrospectedColumn> columns, String prefix) {
        return generateValuesSelective(columns, prefix, true);
    }

    /**
     * 生成values Selective Ele
     * @param columns
     * @param prefix
     * @param bracket
     * @return
     */
    public static XmlElement generateValuesSelective(List<IntrospectedColumn> columns, String prefix, boolean bracket) {
        return generateCommColumnsSelective(columns, prefix, bracket, 2);
    }

    /**
     * 生成sets Ele
     * @param columns
     * @return
     */
    public static List<Element> generateSets(List<IntrospectedColumn> columns) {
        return generateSets(columns, null);
    }

    /**
     * 生成sets Ele
     * @param columns
     * @param prefix
     * @return
     */
    public static List<Element> generateSets(List<IntrospectedColumn> columns, String prefix) {
        return generateCommColumns(columns, prefix, false, 3);
    }

    /**
     * 生成sets Selective Ele
     * @param columns
     * @return
     */
    public static XmlElement generateSetsSelective(List<IntrospectedColumn> columns) {
        return generateSetsSelective(columns, null);
    }

    /**
     * 生成sets Selective Ele
     * @param columns
     * @param prefix
     * @return
     */
    public static XmlElement generateSetsSelective(List<IntrospectedColumn> columns, String prefix) {
        return generateCommColumnsSelective(columns, prefix, false, 3);
    }

    /**
     * 生成keys Ele (upsert)
     * @param columns
     * @param prefix
     * @return
     */
    public static List<Element> generateUpsertKeys(List<IntrospectedColumn> columns, String prefix) {
        return generateCommColumns(columns, prefix, true, 1, true);
    }

    /**
     * 生成values Ele (upsert)
     * @param columns
     * @param prefix
     * @param bracket
     * @return
     */
    public static List<Element> generateUpsertValues(List<IntrospectedColumn> columns, String prefix, boolean bracket) {
        return generateCommColumns(columns, prefix, bracket, 2, true);
    }

    /**
     * 生成sets Ele (upsert)
     * @param columns
     * @param prefix
     * @return
     */
    public static List<Element> generateUpsertSets(List<IntrospectedColumn> columns, String prefix) {
        return generateCommColumns(columns, prefix, false, 3, true);
    }

    /**
     * 通用遍历columns
     * @param columns
     * @param prefix
     * @param bracket
     * @param type    1:key,2:value,3:set
     * @return
     */
    private static List<Element> generateCommColumns(List<IntrospectedColumn> columns, String prefix, boolean bracket, int type) {
        return generateCommColumns(columns, prefix, bracket, type, false);
    }

    /**
     * 通用遍历columns
     * @param columns
     * @param prefix
     * @param bracket
     * @param type    1:key,2:value,3:set
     * @param upsert
     * @return
     */
    private static List<Element> generateCommColumns(List<IntrospectedColumn> columns, String prefix, boolean bracket, int type, boolean upsert) {
        List<Element> list = new ArrayList<>();

        // 只有upsert插件才会传入 IdentityAndGeneratedAlwaysColumn
        if (upsert && hasIdentityAndGeneratedAlwaysColumns(columns)) {
            XmlElement trimEle = generateTrim(bracket);

            for (IntrospectedColumn introspectedColumn : columns) {
                if (introspectedColumn.isGeneratedAlways() || introspectedColumn.isIdentity()) {
                    generateSelectiveToTrimEleTo(trimEle, introspectedColumn, prefix, type);
                } else {
                    generateSelectiveCommColumnTo(trimEle, introspectedColumn, prefix, type);
                }
            }

            return Arrays.asList(trimEle);
        } else {
            StringBuilder sb = new StringBuilder(bracket ? "(" : "");
            Iterator<IntrospectedColumn> columnIterator = columns.iterator();
            while (columnIterator.hasNext()) {
                IntrospectedColumn introspectedColumn = columnIterator.next();

                switch (type) {
                    case 3:
                        List<Element> incrementEles = PluginTools.getHook(IIncrementsPluginHook.class).incrementSetElementGenerated(introspectedColumn, prefix, false);
                        if (!incrementEles.isEmpty()) {
                            // 增量插件支持
                            if (sb.length() > 0) {
                                list.add(new TextElement(sb.toString()));
                                sb.setLength(0);
                            }
                            list.addAll(incrementEles);
                        } else {
                            sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn));
                            sb.append(" = ");
                            sb.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, prefix));
                        }

                        break;
                    case 2:
                        sb.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, prefix));
                        break;
                    case 1:
                        sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn));
                        break;
                }

                if (columnIterator.hasNext()) {
                    sb.append(", ");
                }

                // 保持和官方一致 80 进行换行
                if (type == 1 || type == 2) {
                    if (sb.length() > 80) {
                        list.add(new TextElement(sb.toString()));
                        sb.setLength(0);
                        OutputUtilities.xmlIndent(sb, 1);
                    }
                } else {
                    list.add(new TextElement(sb.toString()));
                    sb.setLength(0);
                }
            }
            if (sb.length() > 0 || bracket) {
                list.add(new TextElement(sb.append(bracket ? ")" : "").toString()));
            }

            return list;
        }
    }

    /**
     * 通用遍历columns
     * @param columns
     * @param prefix
     * @param bracket
     * @param type    1:key,2:value,3:set
     * @return
     */
    private static XmlElement generateCommColumnsSelective(List<IntrospectedColumn> columns, String prefix, boolean bracket, int type) {
        XmlElement trimEle = generateTrim(bracket);
        for (IntrospectedColumn introspectedColumn : columns) {
            generateSelectiveToTrimEleTo(trimEle, introspectedColumn, prefix, type);
        }
        return trimEle;
    }

    /**
     * trim 节点
     * @param bracket
     * @return
     */
    private static XmlElement generateTrim(boolean bracket) {
        XmlElement trimEle = new XmlElement("trim");
        if (bracket) {
            trimEle.addAttribute(new Attribute("prefix", "("));
            trimEle.addAttribute(new Attribute("suffix", ")"));
            trimEle.addAttribute(new Attribute("suffixOverrides", ","));
        } else {
            trimEle.addAttribute(new Attribute("suffixOverrides", ","));
        }
        return trimEle;
    }

    /**
     * 生成选择列到trim 节点
     * @param trimEle
     * @param introspectedColumn
     * @param prefix
     * @param type               1:key,2:value,3:set
     */
    private static void generateSelectiveToTrimEleTo(XmlElement trimEle, IntrospectedColumn introspectedColumn, String prefix, int type) {
        if (type != 3 && (introspectedColumn.isSequenceColumn() || introspectedColumn.getFullyQualifiedJavaType().isPrimitive())) {
            // if it is a sequence column, it is not optional
            // This is required for MyBatis3 because MyBatis3 parses
            // and calculates the SQL before executing the selectKey

            // if it is primitive, we cannot do a null check
            generateSelectiveCommColumnTo(trimEle, introspectedColumn, prefix, type);
        } else {
            XmlElement eleIf = new XmlElement("if");
            eleIf.addAttribute(new Attribute("test", introspectedColumn.getJavaProperty(prefix) + " != null"));

            generateSelectiveCommColumnTo(eleIf, introspectedColumn, prefix, type);

            trimEle.addElement(eleIf);
        }
    }

    /**
     * 生成
     * @param element
     * @param introspectedColumn
     * @param prefix
     * @param type               1:key,2:value,3:set
     */
    private static void generateSelectiveCommColumnTo(XmlElement element, IntrospectedColumn introspectedColumn, String prefix, int type) {
        switch (type) {
            case 3:
                List<Element> incrementEles = PluginTools.getHook(IIncrementsPluginHook.class).incrementSetElementGenerated(introspectedColumn, prefix, true);
                if (!incrementEles.isEmpty()) {
                    // 增量插件支持
                    for (Element ele : incrementEles) {
                        element.addElement(ele);
                    }
                } else {
                    element.addElement(new TextElement(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn) + " = " + MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, prefix) + ","));
                }
                break;
            case 2:
                element.addElement(new TextElement(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, prefix) + ","));
                break;
            case 1:
                element.addElement(new TextElement(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn) + ","));
                break;
        }
    }

    /**
     * 生成 xxxByPrimaryKey 的where 语句
     * @param element
     * @param primaryKeyColumns
     * @return
     */
    public static void generateWhereByPrimaryKeyTo(XmlElement element, List<IntrospectedColumn> primaryKeyColumns) {
        generateWhereByPrimaryKeyTo(element, primaryKeyColumns, null);
    }

    /**
     * 生成 xxxByPrimaryKey 的where 语句
     * @param element
     * @param primaryKeyColumns
     * @param prefix
     * @return
     */
    public static void generateWhereByPrimaryKeyTo(XmlElement element, List<IntrospectedColumn> primaryKeyColumns, String prefix) {
        StringBuilder sb = new StringBuilder();
        boolean and = false;
        for (IntrospectedColumn introspectedColumn : primaryKeyColumns) {
            sb.setLength(0);
            if (and) {
                sb.append("  and ");
            } else {
                sb.append("where ");
                and = true;
            }

            sb.append(MyBatis3FormattingUtilities.getEscapedColumnName(introspectedColumn));
            sb.append(" = ");
            sb.append(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, prefix));
            element.addElement(new TextElement(sb.toString()));
        }
    }

    /**
     * 是否存在自增或者生成的column
     * @param columns
     * @return
     */
    private static boolean hasIdentityAndGeneratedAlwaysColumns(List<IntrospectedColumn> columns) {
        for (IntrospectedColumn ic : columns) {
            if (ic.isGeneratedAlways() || ic.isIdentity()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 生成resultMap的result 节点
     * @param name
     * @param introspectedColumn
     * @return
     */
    public static XmlElement generateResultMapResultElement(String name, IntrospectedColumn introspectedColumn) {
        XmlElement resultElement = new XmlElement(name);

        resultElement.addAttribute(new Attribute("column", MyBatis3FormattingUtilities.getRenamedColumnNameForResultMap(introspectedColumn)));
        resultElement.addAttribute(new Attribute("property", introspectedColumn.getJavaProperty()));
        resultElement.addAttribute(new Attribute("jdbcType", introspectedColumn.getJdbcTypeName()));

        if (stringHasValue(introspectedColumn.getTypeHandler())) {
            resultElement.addAttribute(new Attribute("typeHandler", introspectedColumn.getTypeHandler()));
        }
        return resultElement;
    }

    /**
     * 生成逻辑删除列的删除值
     * @param logicalDeleteColumn
     * @param value
     * @return
     */
    public static String generateLogicalDeleteColumnValue(IntrospectedColumn logicalDeleteColumn, String value) {
        StringBuilder sb = new StringBuilder();
        // 判断字段类型
        if (value == null || "NULL".equalsIgnoreCase(value)) {
            sb.append("NULL");
        } else if (logicalDeleteColumn.isStringColumn()) {
            sb.append("'");
            sb.append(value);
            sb.append("'");
        } else if (logicalDeleteColumn.getFullyQualifiedJavaType().getFullyQualifiedName().equals(Long.class.getName())){
            sb.append(value.replaceAll("L|l", ""));
        } else if (logicalDeleteColumn.getFullyQualifiedJavaType().getFullyQualifiedName().equals(Float.class.getName())){
            sb.append(value.replaceAll("F|f", ""));
        } else {
            sb.append(value);
        }
        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.utils.hook.HookAggregator;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.PluginConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * ---------------------------------------------------------------------------
 * 插件工具集
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/4/20 15:43
 * ---------------------------------------------------------------------------
 */
public class PluginTools {
    private static final Logger logger = LoggerFactory.getLogger(PluginTools.class);

    /**
     * 获取挂载
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getHook(Class<T> clazz) {
        return (T) HookAggregator.getInstance();
    }

    /**
     * 检查插件依赖
     * @param context 上下文
     * @param plugins 插件
     * @return
     */
    public static boolean checkDependencyPlugin(Context context, Class... plugins) {
        for (Class plugin : plugins) {
            if (getPluginIndex(context, plugin) < 0) {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取插件所在位置
     * @param context 上下文
     * @param plugin  插件
     * @return -1:未找到
     */
    public static int getPluginIndex(Context context, Class plugin) {
        List<PluginConfiguration> list = getConfigPlugins(context);
        // 检查是否配置了ModelColumnPlugin插件
        for (int i = 0; i < list.size(); i++) {
            PluginConfiguration config = list.get(i);
            if (plugin.getName().equals(config.getConfigurationType())) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 获取插件列表
     * @param ctx 上下文
     * @return
     */
    public static List<PluginConfiguration> getConfigPlugins(Context ctx) {
        try {
            // 利用反射获取pluginConfigurations属性
            return (List<PluginConfiguration>) BeanUtils.getProperty(ctx, "pluginConfigurations");
        } catch (Exception e) {
            logger.error("插件检查反射异常", e);
        }
        return new ArrayList<>();
    }

    /**
     * 获取插件配置
     * @param context 上下文
     * @param plugin  插件
     * @return
     */
    public static PluginConfiguration getPluginConfiguration(Context context, Class plugin) {
        int index = getPluginIndex(context, plugin);
        if (index > -1) {
            return getConfigPlugins(context).get(index);
        }
        return null;
    }

    /**
     * 版本号比较
     * @param v1
     * @param v2
     * @return 0代表相等，1代表左边大，-1代表右边大
     * Utils.compareVersion("1.0.358_20180820090554","1.0.358_20180820090553")=1
     */
    public static int compareVersion(String v1, String v2) {
        if (v1.equals(v2)) {
            return 0;
        }
        String[] version1Array = v1.split("[._]");
        String[] version2Array = v2.split("[._]");
        int index = 0;
        int minLen = Math.min(version1Array.length, version2Array.length);
        long diff = 0;

        while (index < minLen
                && (diff = Long.parseLong(version1Array[index])
                - Long.parseLong(version2Array[index])) == 0) {
            index++;
        }
        if (diff == 0) {
            for (int i = index; i < version1Array.length; i++) {
                if (Long.parseLong(version1Array[i]) > 0) {
                    return 1;
                }
            }

            for (int i = index; i < version2Array.length; i++) {
                if (Long.parseLong(version2Array[i]) > 0) {
                    return -1;
                }
            }
            return 0;
        } else {
            return diff > 0 ? 1 : -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.BasePlugin;
import com.itfsw.mybatis.generator.plugins.utils.FormatTools;
import com.itfsw.mybatis.generator.plugins.utils.IntrospectedTableTools;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.config.TableConfiguration;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * ---------------------------------------------------------------------------
 * table 重命名插件
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/6/6 15:18
 * ---------------------------------------------------------------------------
 */
@Deprecated
public class TableRenamePlugin extends BasePlugin {
    public static final String PRO_SEARCH_STRING = "searchString";  // 查找 property
    public static final String PRO_REPLACE_STRING = "replaceString";  // 替换 property
    public static final String PRO_TABLE_OVERRIDE = "tableOverride";   // table 重命名 property

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

        // 如果配置了searchString 或者 replaceString，二者不允许单独存在
        if ((getProperties().getProperty(PRO_SEARCH_STRING) == null && getProperties().getProperty(PRO_REPLACE_STRING) != null)
                || (getProperties().getProperty(PRO_SEARCH_STRING) != null && getProperties().getProperty(PRO_REPLACE_STRING) == null)) {
            warnings.add("itfsw:插件" + this.getClass().getTypeName() + "插件的searchString、replaceString属性需配合使用，不能单独存在！");
            return false;
        }

        // 如果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. 获取表单独配置
        if (introspectedTable.getTableConfigurationProperty(PRO_TABLE_OVERRIDE) != null) {
            String override = introspectedTable.getTableConfigurationProperty(PRO_TABLE_OVERRIDE);
            try {
                IntrospectedTableTools.setDomainObjectName(introspectedTable, getContext(), override);
            } catch (Exception e) {
                logger.error("itfsw:插件" + this.getClass().getTypeName() + "使用tableOverride替换时异常！", e);
            }
        } else if (getProperties().getProperty(PRO_SEARCH_STRING) != null) {
            String searchString = getProperties().getProperty(PRO_SEARCH_STRING);
            String replaceString = getProperties().getProperty(PRO_REPLACE_STRING);

            String domainObjectName = introspectedTable.getFullyQualifiedTable().getDomainObjectName();
            Pattern pattern = Pattern.compile(searchString);
            Matcher matcher = pattern.matcher(domainObjectName);
            domainObjectName = matcher.replaceAll(replaceString);
            // 命名规范化 首字母大写
            domainObjectName = FormatTools.upFirstChar(domainObjectName);
            try {
                IntrospectedTableTools.setDomainObjectName(introspectedTable, getContext(), domainObjectName);
            } catch (Exception e) {
                logger.error("itfsw:插件" + this.getClass().getTypeName() + "使用searchString、replaceString替换时异常！", e);
            }
        }
        super.initialized(introspectedTable);
    }
}


/*
 * 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.ModelColumnPlugin;
import org.mybatis.generator.api.CommentGenerator;
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 java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

/**
 * ---------------------------------------------------------------------------
 * 格式化工具，优化输出
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/6/30 10:53
 * ---------------------------------------------------------------------------
 */
public class FormatTools {
    /**
     * 在最佳位置添加方法
     * @param innerClass
     * @param method
     */
    public static void addMethodWithBestPosition(InnerClass innerClass, Method method) {
        addMethodWithBestPosition(method, innerClass.getMethods());
    }

    /**
     * 在最佳位置添加方法
     * @param interfacz
     * @param method
     */
    public static void addMethodWithBestPosition(Interface interfacz, Method method) {
        // import
        Set<FullyQualifiedJavaType> importTypes = new TreeSet<>();
        // 返回
        if (method.getReturnType() != null) {
            importTypes.add(method.getReturnType());
            importTypes.addAll(method.getReturnType().getTypeArguments());
        }
        // 参数 比较特殊的是ModelColumn生成的Column
        for (Parameter parameter : method.getParameters()) {
            boolean flag = true;
            for (String annotation : parameter.getAnnotations()) {
                if (annotation.startsWith("@Param")) {
                    importTypes.add(new FullyQualifiedJavaType("org.apache.ibatis.annotations.Param"));

                    if (annotation.matches(".*selective.*") && parameter.getType().getShortName().equals(ModelColumnPlugin.ENUM_NAME)) {
                        flag = false;
                    }
                }
            }
            if (flag) {
                importTypes.add(parameter.getType());
                importTypes.addAll(parameter.getType().getTypeArguments());
            }
        }
        interfacz.addImportedTypes(importTypes);
        addMethodWithBestPosition(method, interfacz.getMethods());
    }

    /**
     * 在最佳位置添加方法
     * @param innerEnum
     * @param method
     */
    public static void addMethodWithBestPosition(InnerEnum innerEnum, Method method) {
        addMethodWithBestPosition(method, innerEnum.getMethods());
    }

    /**
     * 在最佳位置添加方法
     * @param topLevelClass
     * @param method
     */
    public static void addMethodWithBestPosition(TopLevelClass topLevelClass, Method method) {
        addMethodWithBestPosition(method, topLevelClass.getMethods());
    }

    /**
     * 在最佳位置添加节点
     * @param rootElement
     * @param element
     */
    public static void addElementWithBestPosition(XmlElement rootElement, XmlElement element) {
        // sql 元素都放在sql后面
        if (element.getName().equals("sql")) {
            int index = 0;
            for (Element ele : rootElement.getElements()) {
                if (ele instanceof XmlElement && ((XmlElement) ele).getName().equals("sql")) {
                    index++;
                }
            }
            rootElement.addElement(index, element);
        } else {

            // 根据id 排序
            String id = getIdFromElement(element);
            if (id == null) {
                rootElement.addElement(element);
            } else {
                List<Element> elements = rootElement.getElements();
                int index = -1;
                for (int i = 0; i < elements.size(); i++) {
                    Element ele = elements.get(i);
                    if (ele instanceof XmlElement) {
                        String eleId = getIdFromElement((XmlElement) ele);
                        if (eleId != null) {
                            if (eleId.startsWith(id)) {
                                if (index == -1) {
                                    index = i;
                                }
                            } else if (id.startsWith(eleId)) {
                                index = i + 1;
                            }
                        }
                    }
                }

                if (index == -1 || index >= elements.size()) {
                    rootElement.addElement(element);
                } else {
                    elements.add(index, element);
                }
            }
        }
    }

    /**
     * 找出节点ID值
     * @param element
     * @return
     */
    private static String getIdFromElement(XmlElement element) {
        for (Attribute attribute : element.getAttributes()) {
            if (attribute.getName().equals("id")) {
                return attribute.getValue();
            }
        }
        return null;
    }

    /**
     * 获取最佳添加位置
     * @param method
     * @param methods
     * @return
     */
    private static void addMethodWithBestPosition(Method method, List<Method> methods) {
        int index = -1;
        for (int i = 0; i < methods.size(); i++) {
            Method m = methods.get(i);
            if (m.getName().equals(method.getName())) {
                if (m.getParameters().size() <= method.getParameters().size()) {
                    index = i + 1;
                } else {
                    index = i;
                }
            } else if (m.getName().startsWith(method.getName())) {
                if (index == -1) {
                    index = i;
                }
            } else if (method.getName().startsWith(m.getName())) {
                index = i + 1;
            }
        }
        if (index == -1 || index >= methods.size()) {
            methods.add(methods.size(), method);
        } else {
            methods.add(index, method);
        }
    }

    /**
     * 替换已有方法注释
     * @param commentGenerator
     * @param method
     * @param introspectedTable
     */
    public static void replaceGeneralMethodComment(CommentGenerator commentGenerator, Method method, IntrospectedTable introspectedTable) {
        method.getJavaDocLines().clear();
        commentGenerator.addGeneralMethodComment(method, introspectedTable);
    }

    /**
     * 替换已有注释
     * @param commentGenerator
     * @param element
     */
    public static void replaceComment(CommentGenerator commentGenerator, XmlElement element) {
        Iterator<Element> elementIterator = element.getElements().iterator();
        boolean flag = false;
        while (elementIterator.hasNext()) {
            Element ele = elementIterator.next();
            if (ele instanceof TextElement && ((TextElement) ele).getContent().matches(".*<!--.*")) {
                flag = true;
            }

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

            if (ele instanceof TextElement && ((TextElement) ele).getContent().matches(".*-->.*")) {
                flag = false;
            }
        }

        XmlElement tmpEle = new XmlElement("tmp");
        commentGenerator.addComment(tmpEle);

        for (int i = tmpEle.getElements().size() - 1; i >= 0; i--) {
            element.addElement(0, tmpEle.getElements().get(i));
        }
    }

    /**
     * 首字母大写
     * @param str
     * @return
     */
    public static String upFirstChar(String str) {
        return str.substring(0, 1).toUpperCase() + str.substring(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 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.StringUtility;

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

/**
 * ---------------------------------------------------------------------------
 * 批量插入插件
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/1/13 9:33
 * ---------------------------------------------------------------------------
 */
public class BatchInsertPlugin extends BasePlugin {
    public static final String METHOD_BATCH_INSERT = "batchInsert";  // 方法名
    public static final String METHOD_BATCH_INSERT_SELECTIVE = "batchInsertSelective";  // 方法名
    public static final String PRO_ALLOW_MULTI_QUERIES = "allowMultiQueries";   // property allowMultiQueries
    private boolean allowMultiQueries = false;  // 是否允许多sql提交

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

        // 插件使用前提是数据库为MySQL或者SQLserver，因为返回主键使用了JDBC的getGenereatedKeys方法获取主键
        if ("com.mysql.jdbc.Driver".equalsIgnoreCase(this.getContext().getJdbcConnectionConfiguration().getDriverClass()) == false
                && "com.microsoft.jdbc.sqlserver.SQLServer".equalsIgnoreCase(this.getContext().getJdbcConnectionConfiguration().getDriverClass()) == false
                && "com.microsoft.sqlserver.jdbc.SQLServerDriver".equalsIgnoreCase(this.getContext().getJdbcConnectionConfiguration().getDriverClass()) == false
                && "com.mysql.cj.jdbc.Driver".equalsIgnoreCase(this.getContext().getJdbcConnectionConfiguration().getDriverClass()) == false) {
            warnings.add("itfsw:插件" + this.getClass().getTypeName() + "插件使用前提是数据库为MySQL或者SQLserver，因为返回主键使用了JDBC的getGenereatedKeys方法获取主键！");
            return false;
        }

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

        // 插件是否开启了多sql提交
        Properties properties = this.getProperties();
        String allowMultiQueries = properties.getProperty(PRO_ALLOW_MULTI_QUERIES);
        this.allowMultiQueries = allowMultiQueries == null ? false : StringUtility.isTrue(allowMultiQueries);
        if (this.allowMultiQueries) {
            // 提示用户注意信息
            warnings.add("itfsw:插件" + this.getClass().getTypeName() + "插件您开启了allowMultiQueries支持，注意在jdbc url 配置中增加“allowMultiQueries=true”支持（不怎么建议使用该功能，开启多sql提交会增加sql注入的风险，请确保你所有sql都使用MyBatis书写，请不要使用statement进行sql提交）！");
        }

        return super.validate(warnings);
    }

    /**
     * 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) {
        // 1. batchInsert
        FullyQualifiedJavaType listType = FullyQualifiedJavaType.getNewListInstance();
        listType.addTypeArgument(introspectedTable.getRules().calculateAllFieldsClass());
        Method mBatchInsert = JavaElementGeneratorTools.generateMethod(
                METHOD_BATCH_INSERT,
                JavaVisibility.DEFAULT,
                FullyQualifiedJavaType.getIntInstance(),
                new Parameter(listType, "list", "@Param(\"list\")")

        );
        commentGenerator.addGeneralMethodComment(mBatchInsert, introspectedTable);
        // interface 增加方法
        FormatTools.addMethodWithBestPosition(interfaze, mBatchInsert);
        logger.debug("itfsw(批量插入插件):" + interfaze.getType().getShortName() + "增加batchInsert方法。");

        // 2. batchInsertSelective
        FullyQualifiedJavaType selectiveType = new FullyQualifiedJavaType(introspectedTable.getRules().calculateAllFieldsClass().getShortName() + "." + ModelColumnPlugin.ENUM_NAME);
        Method mBatchInsertSelective = JavaElementGeneratorTools.generateMethod(
                METHOD_BATCH_INSERT_SELECTIVE,
                JavaVisibility.DEFAULT,
                FullyQualifiedJavaType.getIntInstance(),
                new Parameter(listType, "list", "@Param(\"list\")"),
                new Parameter(selectiveType, "selective", "@Param(\"selective\")", true)
        );
        commentGenerator.addGeneralMethodComment(mBatchInsertSelective, introspectedTable);
        // interface 增加方法
        FormatTools.addMethodWithBestPosition(interfaze, mBatchInsertSelective);
        logger.debug("itfsw(批量插入插件):" + interfaze.getType().getShortName() + "增加batchInsertSelective方法。");

        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) {
        // 1. batchInsert
        XmlElement batchInsertEle = new XmlElement("insert");
        batchInsertEle.addAttribute(new Attribute("id", METHOD_BATCH_INSERT));
        // 参数类型
        batchInsertEle.addAttribute(new Attribute("parameterType", "map"));
        // 添加注释(!!!必须添加注释，overwrite覆盖生成时，@see XmlFileMergerJaxp.isGeneratedNode会去判断注释中是否存在OLD_ELEMENT_TAGS中的一点，例子：@mbg.generated)
        commentGenerator.addComment(batchInsertEle);

        // 使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。所以只支持MYSQL和SQLServer
        XmlElementGeneratorTools.useGeneratedKeys(batchInsertEle, introspectedTable);

        batchInsertEle.addElement(new TextElement("insert into " + introspectedTable.getFullyQualifiedTableNameAtRuntime()));
        for (Element element : XmlElementGeneratorTools.generateKeys(ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns()), true)) {
            batchInsertEle.addElement(element);
        }

        // 添加foreach节点
        XmlElement foreachElement = new XmlElement("foreach");
        foreachElement.addAttribute(new Attribute("collection", "list"));
        foreachElement.addAttribute(new Attribute("item", "item"));
        foreachElement.addAttribute(new Attribute("separator", ","));

        for (Element element : XmlElementGeneratorTools.generateValues(ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns()), "item.")) {
            foreachElement.addElement(element);
        }

        // values 构建
        batchInsertEle.addElement(new TextElement("values"));
        batchInsertEle.addElement(foreachElement);
        document.getRootElement().addElement(batchInsertEle);
        logger.debug("itfsw(批量插入插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加batchInsert实现方法。");

        // 2. batchInsertSelective
        XmlElement batchInsertSelectiveEle = new XmlElement("insert");
        // 添加注释(!!!必须添加注释，overwrite覆盖生成时，@see XmlFileMergerJaxp.isGeneratedNode会去判断注释中是否存在OLD_ELEMENT_TAGS中的一点，例子：@mbg.generated)
        commentGenerator.addComment(batchInsertSelectiveEle);

        batchInsertSelectiveEle.addAttribute(new Attribute("id", METHOD_BATCH_INSERT_SELECTIVE));
        // 参数类型
        batchInsertSelectiveEle.addAttribute(new Attribute("parameterType", "map"));

        // 使用JDBC的getGenereatedKeys方法获取主键并赋值到keyProperty设置的领域模型属性中。所以只支持MYSQL和SQLServer
        // issues#70 mybatis 版本升级到3.5.0之后，useGeneratedKeys在配置keyProperty时需要指定前缀
        XmlElementGeneratorTools.useGeneratedKeys(batchInsertSelectiveEle, introspectedTable, PluginTools.compareVersion(mybatisVersion, "3.5.0") >= 0 ? "list." : null);

        // 支持原生字段非空判断
        if (this.allowMultiQueries) {
            XmlElement chooseEle = new XmlElement("choose");

            // selective 增强
            XmlElement selectiveEnhancedEle = new XmlElement("when");
            selectiveEnhancedEle.addAttribute(new Attribute("test", "selective != null and selective.length > 0"));
            chooseEle.addElement(selectiveEnhancedEle);

            selectiveEnhancedEle.getElements().addAll(this.generateSelectiveEnhancedEles(introspectedTable));

            // 原生非空判断语句
            XmlElement selectiveNormalEle = new XmlElement("otherwise");
            chooseEle.addElement(selectiveNormalEle);

            XmlElement foreachEle = new XmlElement("foreach");
            selectiveNormalEle.addElement(foreachEle);
            foreachEle.addAttribute(new Attribute("collection", "list"));
            foreachEle.addAttribute(new Attribute("item", "item"));
            foreachEle.addAttribute(new Attribute("separator", ";"));

            foreachEle.addElement(new TextElement("insert into " + introspectedTable.getFullyQualifiedTableNameAtRuntime()));

            XmlElement insertTrimElement = new XmlElement("trim");
            foreachEle.addElement(insertTrimElement);
            insertTrimElement.addElement(XmlElementGeneratorTools.generateKeysSelective(ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns()), "item."));

            foreachEle.addElement(new TextElement("values"));

            XmlElement valuesTrimElement = new XmlElement("trim");
            foreachEle.addElement(valuesTrimElement);
            valuesTrimElement.addElement(XmlElementGeneratorTools.generateValuesSelective(ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns()), "item."));

            batchInsertSelectiveEle.addElement(chooseEle);
        } else {
            batchInsertSelectiveEle.getElements().addAll(this.generateSelectiveEnhancedEles(introspectedTable));
        }

        document.getRootElement().addElement(batchInsertSelectiveEle);
        logger.debug("itfsw(批量插入插件):" + introspectedTable.getMyBatis3XmlMapperFileName() + "增加batchInsertSelective实现方法。");

        return true;
    }

    /**
     * 生成insert selective 增强的插入语句
     * @param introspectedTable
     * @return
     */
    private List<Element> generateSelectiveEnhancedEles(IntrospectedTable introspectedTable) {
        List<Element> eles = new ArrayList<>();

        eles.add(new TextElement("insert into " + introspectedTable.getFullyQualifiedTableNameAtRuntime() + " ("));

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

        eles.add(foreachInsertColumns);

        eles.add(new TextElement(")"));

        // values
        eles.add(new TextElement("values"));

        // foreach values
        XmlElement foreachValues = new XmlElement("foreach");
        foreachValues.addAttribute(new Attribute("collection", "list"));
        foreachValues.addAttribute(new Attribute("item", "item"));
        foreachValues.addAttribute(new Attribute("separator", ","));

        foreachValues.addElement(new TextElement("("));

        // foreach 所有插入的列，比较是否存在
        XmlElement foreachInsertColumnsCheck = new XmlElement("foreach");
        foreachInsertColumnsCheck.addAttribute(new Attribute("collection", "selective"));
        foreachInsertColumnsCheck.addAttribute(new Attribute("item", "column"));
        foreachInsertColumnsCheck.addAttribute(new Attribute("separator", ","));

        // 所有表字段
        List<IntrospectedColumn> columns = ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns());
        List<IntrospectedColumn> columns1 = ListUtilities.removeIdentityAndGeneratedAlwaysColumns(introspectedTable.getAllColumns());
        for (int i = 0; i < columns1.size(); i++) {
            IntrospectedColumn introspectedColumn = columns.get(i);
            XmlElement check = new XmlElement("if");
            check.addAttribute(new Attribute("test", "'" + introspectedColumn.getActualColumnName() + "'.toString() == column.value"));
            check.addElement(new TextElement(MyBatis3FormattingUtilities.getParameterClause(introspectedColumn, "item.")));

            foreachInsertColumnsCheck.addElement(check);
        }
        foreachValues.addElement(foreachInsertColumnsCheck);

        foreachValues.addElement(new TextElement(")"));

        eles.add(foreachValues);

        return eles;
    }
}


/*
 * 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.java.CompilationUnit;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.InnerClass;

/**
 * ---------------------------------------------------------------------------
 * 把InnerInterface包装成InnerClass(Mybatis Generator 没有提供内部接口实现)
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/1/12 17:40
 * ---------------------------------------------------------------------------
 */
public class InnerInterfaceWrapperToInnerClass extends InnerClass{
    private InnerInterface innerInterface;  // 内部接口

    public InnerInterfaceWrapperToInnerClass(FullyQualifiedJavaType type) {
        super(type);
    }

    public InnerInterfaceWrapperToInnerClass(String typeName) {
        super(typeName);
    }

    public InnerInterfaceWrapperToInnerClass(InnerInterface innerInterface){
        super(innerInterface.getType());
        this.innerInterface = innerInterface;
    }

    /**
     * 重写获取Java内容方法，调用InnerInterface的实现
     *
     * @param indentLevel
     * @param compilationUnit
     * @return
     */
    @Override
    public String getFormattedContent(int indentLevel, CompilationUnit compilationUnit) {
        return this.innerInterface.getFormattedContent(indentLevel, compilationUnit);
    }

    /**
     * Getter method for property <tt>innerInterface</tt>.
     * @return property value of innerInterface
     * @author hewei
     */
    public InnerInterface getInnerInterface() {
        return innerInterface;
    }

    /**
     * Setter method for property <tt>innerInterface</tt>.
     * @param innerInterface value to be assigned to property innerInterface
     * @author hewei
     */
    public void setInnerInterface(InnerInterface innerInterface) {
        this.innerInterface = innerInterface;
    }
}


/*
 * 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 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 java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * ---------------------------------------------------------------------------
 *
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2018/5/2 17:52
 * ---------------------------------------------------------------------------
 */
public class XmlElementTools {
    /**
     * 获取属性
     * @param element
     * @param name
     */
    public static Attribute getAttribute(XmlElement element, String name) {
        Iterator<Attribute> iterator = element.getAttributes().iterator();
        while (iterator.hasNext()) {
            Attribute attribute = iterator.next();
            if (attribute.getName().equals(name)) {
                return attribute;
            }
        }
        return null;
    }

    /**
     * 移除属性
     * @param element
     * @param name
     */
    public static void removeAttribute(XmlElement element, String name) {
        Iterator<Attribute> iterator = element.getAttributes().iterator();
        while (iterator.hasNext()) {
            Attribute attribute = iterator.next();
            if (attribute.getName().equals(name)) {
                iterator.remove();
            }
        }
    }

    /**
     * 替换属性
     * @param element
     * @param attribute
     */
    public static void replaceAttribute(XmlElement element, Attribute attribute) {
        removeAttribute(element, attribute.getName());
        element.addAttribute(attribute);
    }

    /**
     * xmlElement 替换
     * @param srcEle
     * @param destEle
     */
    public static void replaceXmlElement(XmlElement srcEle, XmlElement destEle) {
        srcEle.setName(destEle.getName());
        srcEle.getAttributes().clear();
        srcEle.getAttributes().addAll(destEle.getAttributes());
        srcEle.getElements().clear();
        srcEle.getElements().addAll(destEle.getElements());
    }

    /**
     * 查找指定xml节点下指定节点名称的元素
     * @param xmlElement
     * @param name
     * @return
     */
    public static List<XmlElement> findXmlElements(XmlElement xmlElement, String name) {
        List<XmlElement> list = new ArrayList<>();
        List<Element> elements = xmlElement.getElements();
        for (Element ele : elements) {
            if (ele instanceof XmlElement) {
                XmlElement xmlElement1 = (XmlElement) ele;
                if (name.equalsIgnoreCase(xmlElement1.getName())) {
                    list.add(xmlElement1);
                }
            }
        }
        return list;
    }

    /**
     * 查询指定xml下所有text xml 节点
     * @param xmlElement
     * @return
     */
    public static List<TextElement> findAllTextElements(XmlElement xmlElement){
        List<TextElement> textElements = new ArrayList<>();
        for (Element element : xmlElement.getElements()){
            if (element instanceof XmlElement){
                textElements.addAll(findAllTextElements((XmlElement) element));
            } else if (element instanceof TextElement){
                textElements.add((TextElement) element);
            }
        }
        return textElements;
    }

    /**
     * 拷贝
     * @param element
     * @return
     */
    public static XmlElement clone(XmlElement element) {
        XmlElement destEle = new XmlElement(element.getName());
        for (Attribute attribute : element.getAttributes()) {
            destEle.addAttribute(XmlElementTools.clone(attribute));
        }
        for (Element ele : element.getElements()) {
            if (ele instanceof XmlElement) {
                destEle.addElement(XmlElementTools.clone((XmlElement) ele));
            } else if (ele instanceof TextElement) {
                destEle.addElement(XmlElementTools.clone((TextElement) ele));
            }
        }
        return destEle;
    }

    /**
     * 拷贝
     * @param attribute
     * @return
     */
    public static Attribute clone(Attribute attribute) {
        return new Attribute(attribute.getName(), attribute.getValue());
    }

    /**
     * 拷贝
     * @param textElement
     * @return
     */
    public static TextElement clone(TextElement textElement) {
        return new TextElement(textElement.getContent());
    }
}


/*
 * 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.PluginTools;
import com.itfsw.mybatis.generator.plugins.utils.enhanced.InnerTypeFullyQualifiedJavaType;
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.internal.util.JavaBeansUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ---------------------------------------------------------------------------
 * 增加Model Builder方法
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2016/12/28 14:56
 * ---------------------------------------------------------------------------
 */
public class ModelBuilderPlugin extends BasePlugin {
    public static final String BUILDER_CLASS_NAME = "Builder";  // Builder 类名
    private Map<IntrospectedTable, InnerTypeFullyQualifiedJavaType> innerClasses = new HashMap<>();

    /**
     * Model Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        // 判断是否有生成Model的WithBLOBs类
        List<IntrospectedColumn> columns = introspectedTable.getRules().generateRecordWithBLOBsClass() ? introspectedTable.getNonBLOBColumns() : introspectedTable.getAllColumns();
        InnerClass innerClass = this.generateModelBuilder(topLevelClass, introspectedTable, columns);
        topLevelClass.addInnerClass(innerClass);
        return super.modelBaseRecordClassGenerated(topLevelClass, introspectedTable);
    }

    /**
     * Model Methods 生成
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelRecordWithBLOBsClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        InnerClass innerClass = this.generateModelBuilder(topLevelClass, introspectedTable, introspectedTable.getAllColumns());
        topLevelClass.addInnerClass(innerClass);
        return super.modelRecordWithBLOBsClassGenerated(topLevelClass, introspectedTable);
    }

    /**
     * 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
     * @param topLevelClass
     * @param introspectedTable
     * @return
     */
    @Override
    public boolean modelPrimaryKeyClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        InnerClass innerClass = this.generateModelBuilder(topLevelClass, introspectedTable, introspectedTable.getPrimaryKeyColumns());
        topLevelClass.addInnerClass(innerClass);
        return super.modelPrimaryKeyClassGenerated(topLevelClass, introspectedTable);
    }

    /**
     * 生成ModelBuilder
     * @param topLevelClass
     * @param introspectedTable
     * @param columns
     * @return
     */
    private InnerClass generateModelBuilder(TopLevelClass topLevelClass, IntrospectedTable introspectedTable, List<IntrospectedColumn> columns) {
        // 生成内部Builder类
        InnerClass innerClass = new InnerClass(BUILDER_CLASS_NAME);
        innerClass.setVisibility(JavaVisibility.PUBLIC);
        innerClass.setStatic(true);

        // 具体执行顺序 http://www.mybatis.org/generator/reference/pluggingIn.html
        // 顺序为 key base withBLOBs
        InnerTypeFullyQualifiedJavaType builderType = new InnerTypeFullyQualifiedJavaType(topLevelClass.getType().getFullyQualifiedName() + "." + BUILDER_CLASS_NAME);
        if (innerClasses.get(introspectedTable) != null) {
            innerClass.setSuperClass(innerClasses.get(introspectedTable));
            innerClasses.remove(introspectedTable);
        }
        innerClasses.put(introspectedTable, builderType);

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

        commentGenerator.addClassComment(innerClass, introspectedTable);
        logger.debug("itfsw(数据Model链式构建插件):" + topLevelClass.getType().getShortName() + "增加内部Builder类。");

        // 构建内部obj变量
        Field f = JavaElementGeneratorTools.generateField("obj", JavaVisibility.PRIVATE, topLevelClass.getType(), null);
        commentGenerator.addFieldComment(f, introspectedTable);
        innerClass.addField(f);

        // 构造构造方法
        Method constructor = new Method(BUILDER_CLASS_NAME);
        constructor.setVisibility(JavaVisibility.PUBLIC);
        constructor.setConstructor(true);
        constructor.addBodyLine(new StringBuilder("this.obj = new ").append(topLevelClass.getType().getShortName()).append("();").toString());
        commentGenerator.addGeneralMethodComment(constructor, introspectedTable);
        FormatTools.addMethodWithBestPosition(innerClass, constructor);
        logger.debug("itfsw(数据Model链式构建插件):" + topLevelClass.getType().getShortName() + ".Builder增加的构造方法。");

        for (IntrospectedColumn introspectedColumn : columns) {
            Field field = JavaBeansUtil.getJavaBeansField(introspectedColumn, context, introspectedTable);
            Method setterMethod = JavaBeansUtil.getJavaBeansSetter(introspectedColumn, context, introspectedTable);

            Method method = JavaElementGeneratorTools.generateMethod(
                    field.getName(),
                    JavaVisibility.PUBLIC,
                    innerClass.getType(),
                    new Parameter(field.getType(), field.getName())
            );
            commentGenerator.addSetterComment(method, introspectedTable, introspectedColumn);
            method = JavaElementGeneratorTools.generateMethodBody(
                    method,
                    "obj." + setterMethod.getName() + "(" + field.getName() + ");",
                    "return this;"
            );

            // hook
            if (PluginTools.getHook(IModelBuilderPluginHook.class).modelBuilderSetterMethodGenerated(method, topLevelClass, innerClass, introspectedColumn, introspectedTable)) {
                FormatTools.addMethodWithBestPosition(innerClass, method);
                logger.debug("itfsw(数据Model链式构建插件):" + topLevelClass.getType().getShortName() + ".Builder增加" + method.getName() + "方法(复合主键)。");
            }
        }

        Method build = JavaElementGeneratorTools.generateMethod(
                "build",
                JavaVisibility.PUBLIC,
                topLevelClass.getType()
        );
        build.addBodyLine("return this.obj;");
        commentGenerator.addGeneralMethodComment(build, introspectedTable);
        FormatTools.addMethodWithBestPosition(innerClass, build);
        logger.debug("itfsw(数据Model链式构建插件):" + topLevelClass.getType().getShortName() + ".Builder增加build方法。");

        // hook
        PluginTools.getHook(IModelBuilderPluginHook.class).modelBuilderClassGenerated(topLevelClass, innerClass, columns, introspectedTable);

        return innerClass;
    }

}


/*
 *
 *  * 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 org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.JavaModelGeneratorConfiguration;

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

/**
 * ---------------------------------------------------------------------------
 * Example类生成位置修改
 * ---------------------------------------------------------------------------
 * @author: hewei
 * @time:2017/1/12 12:36
 * ---------------------------------------------------------------------------
 */
public class ExampleTargetPlugin extends BasePlugin {
    public static final String PRO_TARGET_PACKAGE = "targetPackage";  // 配置targetPackage名
    private String targetPackage;   // 目标包

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean validate(List<String> warnings) {
        // 获取配置的目标package
        Properties properties = getProperties();
        this.targetPackage = properties.getProperty(PRO_TARGET_PACKAGE);
        if (this.targetPackage == null){
            warnings.add("请配置com.itfsw.mybatis.generator.plugins.ExampleTargetPlugin插件的目标包名(targetPackage)！");
            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 exampleType = introspectedTable.getExampleType();
        // 修改包名
        Context context = getContext();
        JavaModelGeneratorConfiguration configuration = context.getJavaModelGeneratorConfiguration();
        String targetPackage = configuration.getTargetPackage();
        String newExampleType = exampleType.replace(targetPackage, this.targetPackage);

        introspectedTable.setExampleType(newExampleType);

        logger.debug("itfsw(Example 目标包修改插件):修改"+introspectedTable.getExampleType()+"的包到"+this.targetPackage);
    }

}