ConverterRowObjects.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.OptionalFlag;
import io.kaumei.jdbc.anno.model.SQLNameDV;
import io.kaumei.jdbc.anno.model.Visibility;
import io.kaumei.jdbc.anno.store.SearchKey;
import io.kaumei.jdbc.anno.store.SourceDV;
import io.kaumei.jdbc.anno.store.StoreID;
import org.jspecify.annotations.Nullable;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.type.TypeMirror;
import java.util.*;
import java.util.function.BiConsumer;

public final class ConverterRowObjects extends Jdbc2JavaConverter.RowConverter implements Jdbc2JavaConverter.Callable {

    record Placeholder(TypeMirror type, SourceDV source, Visibility visibility,
                       ExecutableElement method,
                       OptionalFlag[] optionalFlags,
                       SQLNameDV[] jdbcNames,
                       @Nullable SearchKey[] others) implements Jdbc2JavaConverter.Placeholder {
        @Override
        public Jdbc2JavaConverter resolve(Map<SearchKey, Jdbc2JavaConverter> converter) {
            var converters = new Jdbc2JavaConverter[others.length];
            for (int i = 0; i < others.length; i++) {
                converters[i] = Objects.requireNonNull(converter.get(others[i]));
            }
            return new ConverterRowObjects(this, converters);
        }

        @Override
        public Set<SearchKey> otherPlaceholders() {
            var result = new HashSet<SearchKey>();
            for (var r : others) {
                if (r != null) {
                    result.add(r);
                }
            }
            return result;
        }

        Set<StoreID> invalidDependentConverters(Map<SearchKey, Jdbc2JavaConverter> converters) {
            var result = new HashSet<StoreID>();
            for (var other : others) {
                if (other != null && converters.get(other) instanceof Jdbc2JavaConverter.RowConverter) {
                    result.add(other.toStoreId());
                }
            }
            return result;
        }
    }

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

    private final boolean isMethod;
    private final Element typeElement;
    private final Name methodName;
    private final int paramLength;
    private final SQLNameDV[] jdbcNames;
    private final Jdbc2JavaConverter.ColumnConverter[] converters;
    private final String[] paramNames;
    private final OptionalFlag[] optionalFlags;

    private ConverterRowObjects(Placeholder placeholder, Jdbc2JavaConverter[] converters) {
        super(placeholder.type, placeholder.source, visibility(placeholder.visibility, converters));
        this.isMethod = placeholder.method.getKind() == ElementKind.METHOD;
        this.typeElement = placeholder.method.getEnclosingElement();
        this.methodName = placeholder.method.getSimpleName();

        this.jdbcNames = placeholder.jdbcNames;

        this.converters = new Jdbc2JavaConverter.ColumnConverter[converters.length];
        for (int i = 0; i < converters.length; i++) {
            this.converters[i] = (Jdbc2JavaConverter.ColumnConverter) converters[i];
        }
        this.optionalFlags = placeholder.optionalFlags;
        this.paramLength = placeholder.method.getParameters().size();
        this.paramNames = new String[paramLength];

        for (int i = 0; i < this.paramLength; i++) {
            this.paramNames[i] = placeholder.method.getParameters().get(i).getSimpleName().toString();
        }
    }

    private static Visibility visibility(Visibility visibility, Jdbc2JavaConverter[] converters) {
        var result = visibility;
        for (var converter : converters) {
            result = result.and(converter.visibility());
        }
        return result;
    }

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

    public Set<String> jdbcNames() {
        var result = new HashSet<String>();
        for (var jdbcName : jdbcNames) {
            result.add(jdbcName.value());
        }
        return result;
    }

    @Override
    public void addResultSetToRow(TargetMethod builder, String localVarName, OptionalFlag optional) {
        builder.addComment("ConverterRowObjects.addResultSetToRow", "type", type());
        CodeBlock.Builder args = CodeBlock.builder();
        for (int i = 0; i < paramLength; i++) {
            var tempVarName = builder.tempVarName(paramNames[i]);
            converters[i].addColumnByName(builder, tempVarName, this.jdbcNames[i], this.optionalFlags[i]);
            if (i == 0) {
                args.add("$L", tempVarName);
            } else {
                args.add(", $L", tempVarName);
            }
        }
        this.createRow(builder, localVarName, optional, args);
    }

    public void addResultSetToRow(TargetMethod builder, String localVarName, OptionalFlag optional, Map<String, ColumnIndex> map) {
        builder.addComment("ConverterRowObjects.addResultSetToRow", "type", type(), "map", map);
        CodeBlock.Builder args = CodeBlock.builder();
        for (int i = 0; i < paramLength; i++) {
            var index = map.get(jdbcNames[i].value());
            var tempVarName = builder.tempVarName(paramNames[i]);
            converters[i].addColumnByIndex(builder, tempVarName, index, this.optionalFlags[i]);
            if (i == 0) {
                args.add("$L", tempVarName);
            } else {
                args.add(", $L", tempVarName);
            }
        }
        this.createRow(builder, localVarName, optional, args);
    }

    public void forAll(BiConsumer<String, ColumnConverter> consumer) {
        for (int i = 0; i < paramNames.length; i++) {
            consumer.accept(paramNames[i], converters[i]);
        }
    }

    @Override
    public void addCallToRow(TargetMethod builder, String localVarName, OptionalFlag optional, Map<String, ColumnIndex> map) {
        builder.addComment("ConverterRowObjects.addCallToRow", "type", type(), "map", map);
        CodeBlock.Builder args = CodeBlock.builder();
        for (int i = 0; i < paramLength; i++) {
            var index = map.get(paramNames[i]);
            var tempVarName = builder.tempVarName(paramNames[i]);
            ((Jdbc2JavaConverter.Callable) converters[i]).addCallToRow(builder, tempVarName, this.optionalFlags[i], Map.of(jdbcNames[i].value(), index));
            if (i == 0) {
                args.add("$L", tempVarName);
            } else {
                args.add(", $L", tempVarName);
            }
        }
        this.createRow(builder, localVarName, optional, args);
    }

    private void createRow(TargetMethod builder, String localVarName, OptionalFlag optional, CodeBlock.Builder args) {
        if (isMethod) {
            if (optional.isOptionalType()) {
                builder.addStatement("var $L = $T.of($T.$N($L))",
                        localVarName, Optional.class, this.typeElement, this.methodName, args.build());
            } else {
                builder.addStatement("var $L = $T.$N($L)",
                        localVarName, this.typeElement, this.methodName, args.build());
            }
        } else {
            if (optional.isOptionalType()) {
                builder.addStatement("var $L = $T.of(new $T($L))",
                        localVarName, Optional.class, this.typeElement, args.build());
            } else {
                builder.addStatement("var $L = new $T($L)",
                        localVarName, this.typeElement, args.build());
            }
        }
    }

}