ConverterColumnNative.java

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

import com.palantir.javapoet.CodeBlock;
import io.kaumei.jdbc.anno.gen.TargetMethod;
import io.kaumei.jdbc.anno.model.*;
import io.kaumei.jdbc.anno.store.SourceDV;
import org.jspecify.annotations.Nullable;

import javax.lang.model.type.TypeMirror;
import java.sql.JDBCType;
import java.util.Map;
import java.util.Optional;

import static java.util.Objects.requireNonNull;

final class ConverterColumnNative extends Jdbc2JavaConverter.ColumnConverter implements  Jdbc2JavaConverter.Callable {

    static ConverterColumnNative fromResultSet(TypeMirror type, String methodName, JDBCType jdbcType) {
        var source = SourceDV.generic("ResultSet." + methodName);
        return new ConverterColumnNative(type, source, null, methodName, SQLTypeDV.of(jdbcType));
    }

    static ConverterColumnNative fromStatic(TypeMirror type, Class<?> cls, String methodName, JDBCType jdbcType) {
        var source = SourceDV.generic(cls.getSimpleName() + "." + methodName);
        return new ConverterColumnNative(type, source, cls, methodName, SQLTypeDV.of(jdbcType));
    }

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

    private final @Nullable Class<?> cls;
    private final String methodName;
    private final boolean isPrimitive;
    private final SQLTypeDV sqlType;

    private ConverterColumnNative(TypeMirror type, SourceDV source, @Nullable Class<?> cls, String methodName, SQLTypeDV sqlType) {
        super(type, source, Visibility.PUBLIC_VISIBILITY);
        this.cls = cls;
        this.methodName = requireNonNull(methodName);
        this.isPrimitive = this.type().getKind().isPrimitive();
        this.sqlType = sqlType;
    }

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

    @Override
    public boolean hasSqlType() {
        return true;
    }

    @Override
    public SQLTypeDV sqlType() {
        return sqlType;
    }

    @Override
    public CodeBlock columnNullCheck(String varName) {
        return this.isPrimitive
                ? CodeBlock.of("rs.wasNull()")
                : CodeBlock.of("($L == null || rs.wasNull())", varName);
    }

    @Override
    public CodeBlock callNullCheck(String varName) {
        return this.isPrimitive
                ? CodeBlock.of("stmt.wasNull()")
                : CodeBlock.of("($L == null || stmt.wasNull())", varName);
    }

    @Override
    public void addColumnByIndex(TargetMethod builder, String localVarName, ColumnIndex index, OptionalFlag optional) {
        builder.addComment("ConverterColumnNative.addColumnByIndex", "type", type(), "optional", optional);
        if (cls == null) {
            setLocalVar(builder, localVarName, optional, CodeBlock.of("rs.$N($L)", methodName, index.columnIndexVar()));
        } else {
            setLocalVar(builder, localVarName, optional, CodeBlock.of("$T.$N(rs, $L)", cls, methodName, index.columnIndexVar()));
        }
        if (optional.isNonNull()) {
            builder.beginControlFlow("if($L)", columnNullCheck(localVarName));
            builder.addThrowColumnWasNull(index);
            builder.endControlFlow();
        }
    }

    @Override
    public void addColumnByName(TargetMethod builder, String localVarName, SQLNameDV columnName, OptionalFlag optional) {
        builder.addComment("ConverterColumnNative.addColumnByName", "type", type(), "optional", optional);
        if (cls == null) {
            setLocalVar(builder, localVarName, optional, CodeBlock.of("rs.$N($S)", methodName, columnName.value()));
        } else {
            setLocalVar(builder, localVarName, optional, CodeBlock.of("$T.$N(rs, $S)", cls, methodName, columnName.value()));
        }
        if (optional.isNonNull()) {
            builder.beginControlFlow("if($L)", columnNullCheck(localVarName));
            builder.addStatement("throw new $T($S)", NullPointerException.class, "JDBC column was null on name: " + columnName.value());
            builder.endControlFlow();
        }
    }

    @Override
    public void addCallToRow(TargetMethod builder, String localVarName, OptionalFlag optional, Map<String, ColumnIndex> map) {
        builder.addComment("ConverterColumnNative.addCallableOutput", "type", type(), "map", map, "optional", optional);
        var index = map.values().iterator().next();
        if (cls == null) {
            setLocalVar(builder, localVarName, optional, CodeBlock.of("stmt.$N($L)", methodName, index.columnIndexVar()));
        } else {
            setLocalVar(builder, localVarName, optional, CodeBlock.of("$T.$N(stmt, $L)", cls, methodName, index.columnIndexVar()));
        }
        if (optional.isNonNull()) {
            builder.beginControlFlow("if($L)", callNullCheck(localVarName));
            builder.addThrowColumnWasNull(index);
            builder.endControlFlow();
        }
    }

    private void setLocalVar(TargetMethod builder, String localVarName, OptionalFlag optional, CodeBlock code) {
        if (optional.isOptionalType()) {
            builder.addStatement("var $L = $T.ofNullable($L)", localVarName, Optional.class, code);
        } else {
            builder.addStatement("var $L = $L", localVarName, code);
        }
    }
}