SourceMethod.java

/*
 * SPDX-FileCopyrightText: 2025 kaumei.io
 * SPDX-License-Identifier: Apache-2.0
 */
package io.kaumei.jdbc.anno.model;

import io.kaumei.jdbc.anno.ctx.ConfigService;
import io.kaumei.jdbc.anno.ctx.Context;
import io.kaumei.jdbc.anno.msg.JdbcMsg;
import io.kaumei.jdbc.anno.msg.Msg;
import io.kaumei.jdbc.annotation.JdbcUpdate;
import io.kaumei.jdbc.annotation.config.*;
import org.jspecify.annotations.Nullable;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;

import static java.util.Objects.requireNonNull;

public class SourceMethod {

    public static SourceMethod of(Context ctx, ExecutableElement method) {
        var messages = Msg.builder();
        var annoMap = AnnoMap.of(ctx, method);
        SourceMethodParameter parameter = null;

        // ----- check entry points
        var entryPoint = EntryPoint.of(ctx, annoMap);
        var entryPoints = entryPoint.countEntryPoints();
        if (entryPoints == 0) {
            messages.add(JdbcMsg.jdbcMethodRequiresEntryPointAnnotation());
        } else if (entryPoints > 1) {
            messages.add(JdbcMsg.jdbcMethodRequiresSingleEntryPointAnnotation());
        } else if (entryPoint.selectOpt != null) {
            if (entryPoint.selectOpt.sql().value().isBlank()) {
                messages.add(JdbcMsg.jdbcSelectRequiresSqlString());
            } else {
                parameter = SourceMethodParameter.of(ctx, messages, method, entryPoint.selectOpt.sql());
            }
        } else if (entryPoint.updateOpt != null) {
            if (entryPoint.updateOpt.sql().value().isBlank()) {
                messages.add(JdbcMsg.jdbcUpdateRequiresSqlString());
            } else {
                parameter = SourceMethodParameter.of(ctx, messages, method, entryPoint.updateOpt.sql());
            }
        }

        if (parameter == null) {
            parameter = SourceMethodParameter.of(ctx, messages, method);
        }

        var jdbcName = ctx.JDBC_NAME.getAnnoOpt(method);
        messages.add(jdbcName);
        var jdbcConverterName = ctx.JDBC_CONVERTER_NAME.getAnnoOpt(method);
        if (jdbcConverterName != null && jdbcConverterName.isBlankName()) {
            messages.add(JdbcMsg.annotationValueMustNotBeBlank("@JdbcConverterName"));
        }

        return new SourceMethod(ctx, method, entryPoint, annoMap, parameter, messages.build());
    }

    public record EntryPoint(JavaAnnoType.@Nullable JdbcSelectRecord selectOpt,
                             JavaAnnoType.@Nullable JdbcUpdateRecord updateOpt,
                             JavaAnnoType.@Nullable JdbcNativeRecord nativeJdbcOpt,
                             JavaAnnoType.@Nullable JdbcBatchUpdateRecord batchUpdateOpt) {

        static EntryPoint of(Context ctx, AnnoMap map) {
            return new EntryPoint(map.getOpt(ctx.JDBC_SELECT), map.getOpt(ctx.JDBC_UPDATE), map.getOpt(ctx.JDBC_NATIVE), map.getOpt(ctx.JDBC_BATCH_UPDATE));
        }

        public JavaAnnoType.JdbcUpdateRecord update() {
            return requireNonNull(updateOpt);
        }

        public JavaAnnoType.JdbcNativeRecord nativeJdbc() {
            return requireNonNull(nativeJdbcOpt);
        }

        int countEntryPoints() {
            int count = 0;
            if (selectOpt != null) {
                count++;
            }
            if (updateOpt != null) {
                count++;
            }
            if (nativeJdbcOpt != null) {
                count++;
            }
            if (batchUpdateOpt != null) {
                count++;
            }
            return count;
        }
    }

    // ------------------------------------------------------------------------

    private final Context ctx;
    private final ExecutableElement method;
    private final EntryPoint entryPoint;
    private final AnnoMap annoMap;
    private final SourceMethodParameter parameter;
    private final Msg.Messages messages;

    SourceMethod(Context ctx, ExecutableElement method, EntryPoint entryPoint, AnnoMap annoMap, SourceMethodParameter parameter, Msg.Messages messages) {
        this.ctx = ctx;
        this.method = method;
        this.entryPoint = entryPoint;
        this.annoMap = annoMap;
        this.parameter = parameter;
        this.messages = messages;
    }

    @Override
    public String toString() {
        return method.getEnclosingElement() + "." + method.getSimpleName() + ":" + method.getReturnType();
    }

    // ------------------------------------------------------------------------

    public ExecutableElement method() {
        return method;
    }

    public EntryPoint entryPoint() {
        return entryPoint;
    }

    public AnnoMap annoMap() {
        return annoMap;
    }

    public TypeElement parent() {
        return (TypeElement) this.method.getEnclosingElement();
    }

    public SourceMethodParameter parameter() {
        return parameter;
    }

    public boolean hasMessages() {
        return messages.hasMessages();
    }

    public Msg.Messages messages() {
        return messages;
    }

    public SQLNameDV jdbcName() {
        return annoMap.get(ctx.JDBC_NAME, SQLNameDV.noName());
    }

    public ConverterNameDV jdbcConverterName() {
        return annoMap.get(ctx.JDBC_CONVERTER_NAME, ConverterNameDV.unnamed());
    }

    public JdbcUpdate.GeneratedValues jdbcReturnGeneratedValues() {
        var value = entryPoint.update().returnGeneratedValues();
        if (value == JdbcUpdate.GeneratedValues.DEFAULT) {
            value = this.ctx.kaumeiConfig.jdbcReturnGeneratedValues();
        }
        return value;
    }

    public JdbcNoMoreRows.Kind jdbcNoMoreRows() {
        return this.ctx.kaumeiConfig.searchOnMethod(this.ctx.kaumeiConfig.NO_MORE_ROWS, this);
    }

    public JdbcNoRows.Kind jdbcNoRows() {
        return this.ctx.kaumeiConfig.searchOnMethod(this.ctx.kaumeiConfig.NO_ROWS, this);
    }

    public ConfigService.@Nullable OptionValue<Integer> jdbcFetchSize() {
        return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.FETCH_SIZE, this);
    }

    public ConfigService.@Nullable OptionValue<Integer> jdbcMaxRows() {
        return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.MAX_ROWS, this);
    }

    public ConfigService.@Nullable OptionValue<JdbcResultSetConcurrency.Kind> jdbcResultSetConcurrency() {
        return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.RESULT_SET_CONCURRENCY, this);
    }

    public ConfigService.@Nullable OptionValue<JdbcResultSetType.Kind> jdbcResultSetType() {
        return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.RESULT_SET_TYPE, this);
    }

    public ConfigService.@Nullable OptionValue<JdbcFetchDirection.Kind> jdbcFetchDirection() {
        return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.FETCH_DIRECTION, this);
    }

    public ConfigService.@Nullable OptionValue<Integer> jdbcQueryTimeout() {
        return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.QUERY_TIMEOUT, this);
    }

    public ConfigService.@Nullable OptionValue<Integer> jdbcBatchSize() {
        return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.BATCH_SIZE, this);
    }

    // ------------------------------------------------------------------------

    public Msg.Messages unusedAnno() {
        var messages = Msg.builder();
        if (annoMap.hasUnused()) {
            messages.add(JdbcMsg.javaMethodHasUnusedAnnotations(annoMap.unused()));
        }
        parameter.addUnusedAnno(messages);
        return messages.build();
    }

    // ------------------------------------------------------------------------

    public JdbcTypeMirror<ExecutableElement> returnType() {
        return ctx.jdbcTypeMirror(this.method);
    }

}