DefaultCommentGenerator.java

/*
 *    Copyright 2006-2026 the original author or authors.
 *
 *    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
 *
 *       https://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 org.mybatis.generator.internal;

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

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;

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.OutputUtilities;
import org.mybatis.generator.api.dom.java.Field;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.InnerClass;
import org.mybatis.generator.api.dom.java.InnerEnum;
import org.mybatis.generator.api.dom.java.InnerRecord;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.kotlin.KotlinFile;
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;

public class DefaultCommentGenerator implements CommentGenerator {

    private final Properties properties = new Properties();

    private boolean suppressDate;

    private boolean suppressAllComments;

    private boolean minimizeComments;

    /** If suppressAllComments or minimizeComments is true, this option is ignored. */
    private boolean addRemarkComments;

    private final FullyQualifiedJavaType generatedImport =
            new FullyQualifiedJavaType("jakarta.annotation.Generated"); //$NON-NLS-1$

    public DefaultCommentGenerator() {
        super();
        suppressDate = false;
        suppressAllComments = false;
        addRemarkComments = false;
        minimizeComments = false;
    }

    @Override
    public void addConfigurationProperties(Properties props) {
        this.properties.putAll(props);

        suppressDate = isTrue(properties.getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_DATE));
        suppressAllComments = isTrue(properties.getProperty(PropertyRegistry.COMMENT_GENERATOR_SUPPRESS_ALL_COMMENTS));
        addRemarkComments = isTrue(properties.getProperty(PropertyRegistry.COMMENT_GENERATOR_ADD_REMARK_COMMENTS));
        minimizeComments = isTrue(properties.getProperty(PropertyRegistry.COMMENT_GENERATOR_MINIMIZE_COMMENTS));
    }

    /**
     * Adds a suitable comment to warn users that the element was generated and
     * when it was generated.
     *
     * @param xmlElement the XML element
     */
    @Override
    public void addComment(XmlElement xmlElement) {
        if (suppressAllComments) {
            return;
        }

        xmlElement.addElement(new TextElement("<!--")); //$NON-NLS-1$

        String sb = OutputUtilities.xmlIndent(1)
                + "Generated Code - " //$NON-NLS-1$
                + MergeConstants.NEW_ELEMENT_TAG;
        xmlElement.addElement(new TextElement(sb));

        if (!minimizeComments) {
            xmlElement.addElement(
                    new TextElement(OutputUtilities.xmlIndent(1)
                            + "This element is automatically generated by MyBatis Generator," //$NON-NLS-1$
                            + " do not modify.")); //$NON-NLS-1$
        }

        getDateString().ifPresent(s -> {
            String text = OutputUtilities.xmlIndent(1)
                    + "This element was generated on " //$NON-NLS-1$
                    + s
                    + '.';
            xmlElement.addElement(new TextElement(text));

        });

        xmlElement.addElement(new TextElement("-->")); //$NON-NLS-1$
    }

    /**
     * Returns a formatted date string to include in the Javadoc tag and XML
     * comments. You may return null if you do not want the date in these
     * documentation elements.
     *
     * @return a string representing the current timestamp, or null
     */
    protected Optional<String> getDateString() {
        if (suppressDate || minimizeComments) {
            return Optional.empty();
        } else {
            return Optional.of(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now()));
        }
    }

    @Override
    public void addModelClassComment(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        if (suppressAllComments || minimizeComments) {
            return;
        }

        if (addRemarkComments) {
            introspectedTable.getRemarks().ifPresent(remarks -> {
                topLevelClass.addJavaDocLine("/**"); //$NON-NLS-1$
                topLevelClass.addJavaDocLine(" * Database Table Remarks:"); //$NON-NLS-1$
                String[] remarkLines = remarks.split(System.lineSeparator());
                for (String remarkLine : remarkLines) {
                    topLevelClass.addJavaDocLine(" *   " + remarkLine); //$NON-NLS-1$
                }
                topLevelClass.addJavaDocLine(" */"); //$NON-NLS-1$
            });
        }
    }

    @Override
    public void addGeneralMethodAnnotation(Method method, IntrospectedTable introspectedTable,
            Set<FullyQualifiedJavaType> imports) {
        if (suppressAllComments) {
            return;
        }

        imports.add(generatedImport);
        String comment = "Source Table: " + introspectedTable.getFullyQualifiedTable(); //$NON-NLS-1$
        method.addAnnotation(getGeneratedAnnotation(comment, false));
    }

    @Override
    public void addGeneralMethodAnnotation(Method method, IntrospectedTable introspectedTable,
            IntrospectedColumn introspectedColumn, Set<FullyQualifiedJavaType> imports) {
        if (suppressAllComments) {
            return;
        }

        imports.add(generatedImport);
        String comment = "Source field: " //$NON-NLS-1$
                + introspectedTable.getFullyQualifiedTable() + "." //$NON-NLS-1$
                + introspectedColumn.getActualColumnName();
        method.addAnnotation(getGeneratedAnnotation(comment, false));
    }

    @Override
    public void addFieldAnnotation(Field field, IntrospectedTable introspectedTable,
            Set<FullyQualifiedJavaType> imports) {
        if (suppressAllComments) {
            return;
        }

        imports.add(generatedImport);
        String comment = "Source Table: " + introspectedTable.getFullyQualifiedTable(); //$NON-NLS-1$
        field.addAnnotation(getGeneratedAnnotation(comment, false));
    }

    @Override
    public void addFieldAnnotation(Field field, IntrospectedTable introspectedTable,
            IntrospectedColumn introspectedColumn, Set<FullyQualifiedJavaType> imports) {
        if (suppressAllComments) {
            return;
        }

        imports.add(generatedImport);
        String comment = "Source field: " //$NON-NLS-1$
                + introspectedTable.getFullyQualifiedTable() + "." //$NON-NLS-1$
                + introspectedColumn.getActualColumnName();
        field.addAnnotation(getGeneratedAnnotation(comment, false));

        if (!minimizeComments && addRemarkComments) {
            introspectedColumn.getRemarks().ifPresent(r -> {
                field.addJavaDocLine("/**"); //$NON-NLS-1$
                field.addJavaDocLine(" * Database Column Remarks:"); //$NON-NLS-1$
                String[] remarkLines = r.split(System.lineSeparator());
                for (String remarkLine : remarkLines) {
                    field.addJavaDocLine(" *   " + remarkLine); //$NON-NLS-1$
                }
                field.addJavaDocLine(" */"); //$NON-NLS-1$
            });
        }
    }

    @Override
    public void addClassAnnotation(InnerClass innerClass, IntrospectedTable introspectedTable,
            Set<FullyQualifiedJavaType> imports) {
        if (suppressAllComments) {
            return;
        }

        imports.add(generatedImport);
        String comment = "Source Table: " + introspectedTable.getFullyQualifiedTable(); //$NON-NLS-1$
        innerClass.addAnnotation(getGeneratedAnnotation(comment, false));
    }

    @Override
    public void addClassAnnotationAndMarkAsDoNotDelete(InnerClass innerClass, IntrospectedTable introspectedTable,
                                                       Set<FullyQualifiedJavaType> imports) {
        if (suppressAllComments) {
            return;
        }

        imports.add(generatedImport);
        innerClass.addAnnotation(getGeneratedAnnotation(MergeConstants.DO_NOT_DELETE_DURING_MERGE, true));
    }

    @Override
    public void addRecordAnnotation(InnerRecord innerRecord, IntrospectedTable introspectedTable,
                                    Set<FullyQualifiedJavaType> imports) {
        if (suppressAllComments) {
            return;
        }

        imports.add(generatedImport);
        String comment = "Source Table: " + introspectedTable.getFullyQualifiedTable(); //$NON-NLS-1$
        innerRecord.addAnnotation(getGeneratedAnnotation(comment, false));
    }

    @Override
    public void addEnumAnnotation(InnerEnum innerEnum, IntrospectedTable introspectedTable,
                                  Set<FullyQualifiedJavaType> imports) {
        if (suppressAllComments) {
            return;
        }

        imports.add(generatedImport);
        String comment = "Source Table: " + introspectedTable.getFullyQualifiedTable(); //$NON-NLS-1$
        innerEnum.addAnnotation(getGeneratedAnnotation(comment, false));
    }

    private String getGeneratedAnnotation(String comment, boolean forceComment) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("@Generated("); //$NON-NLS-1$

        if (forceComment || !minimizeComments) {
            buffer.append("value=\""); //$NON-NLS-1$
        } else {
            buffer.append('\"');
        }


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

        getDateString().ifPresent(s -> buffer.append(String.format(", date=\"%s\"", s))); //$NON-NLS-1$

        if (forceComment || !minimizeComments) {
            buffer.append(String.format(", comments=\"%s\"", comment)); //$NON-NLS-1$
        }

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

    @Override
    public void addFileComment(KotlinFile kotlinFile) {
        if (suppressAllComments) {
            return;
        }

        kotlinFile.addFileCommentLine("/*"); //$NON-NLS-1$
        kotlinFile.addFileCommentLine(" * Auto-generated file. Created by MyBatis Generator"); //$NON-NLS-1$
        getDateString().ifPresent(s -> kotlinFile.addFileCommentLine(" * Generation date: " + s));
        kotlinFile.addFileCommentLine(" */"); //$NON-NLS-1$
    }
}