JdbcTypeMirror.java

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

import io.kaumei.jdbc.anno.ctx.Context;
import org.jspecify.annotations.Nullable;

import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import java.util.Objects;

import static io.kaumei.jdbc.anno.ctx.JavaModelUtils.component;

public final class JdbcTypeMirror<T extends Element> {

    public static JdbcTypeMirror<ExecutableElement> of(Context ctx, ExecutableElement element) {
        return of(ctx, element, element.getReturnType());
    }

    //public static JdbcTypeMirror<VariableElement> of(Context ctx, VariableElement element) {
    //    return of(ctx, element, element.asType());
    //}

    //public static JdbcTypeMirror<RecordComponentElement> of(Context ctx, RecordComponentElement element) {
    //    return of(ctx, element, element.asType());
    //}

    private static <T extends Element> JdbcTypeMirror<T> of(Context ctx, T element, TypeMirror type) {
        var optional = ctx.optionalFlag(element, type);
        if (optional.isOptionalType()) {
            type = component(type);
        }

        switch (type.getKind()) {
            case VOID:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.VOID, element, type, optional);
            case BOOLEAN:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.BOOLEAN, element, type, optional);
            case BYTE:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.BYTE, element, type, optional);
            case SHORT:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.SHORT, element, type, optional);
            case INT:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.INT, element, type, optional);
            case LONG:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.LONG, element, type, optional);
            case CHAR:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.CHAR, element, type, optional);
            case FLOAT:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.FLOAT, element, type, optional);
            case DOUBLE:
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.DOUBLE, element, type, optional);
            case ARRAY:
                var cmpArray = ctx.resolveComponent(element, type);
                return new JdbcTypeMirror<>(ctx, JdbcTypeKind.ARRAY, element, type, optional, cmpArray);
            case DECLARED:
                var declared = ((DeclaredType) type);
                if (ctx.JAVA_List.isSameType(declared)) {
                    var cmp = ctx.resolveComponent(element, type);
                    return new JdbcTypeMirror<>(ctx, JdbcTypeKind.LIST, element, type, optional, cmp);
                } else if (ctx.JAVA_Stream.isSameType(declared)) {
                    var cmp = ctx.resolveComponent(element, type);
                    return new JdbcTypeMirror<>(ctx, JdbcTypeKind.STREAM, element, type, optional, cmp);
                } else if (ctx.KAUMEI_JDBC_JdbcIterable.isSameType(declared)) {
                    var cmp = ctx.resolveComponent(element, type);
                    return new JdbcTypeMirror<>(ctx, JdbcTypeKind.KAUMEI_JDBC_ITERABLE, element, type, optional, cmp);
                } else if (ctx.KAUMEI_JDBC_JdbcRow.isSameType(declared)) {
                    var cmp = ctx.resolveComponent(element, type);
                    return new JdbcTypeMirror<>(ctx, JdbcTypeKind.KAUMEI_JDBC_ROW, element, type, optional, cmp);
                } else if (ctx.KAUMEI_JDBC_JdbcResultSet.isSameType(declared)) {
                    var cmp = ctx.resolveComponent(element, type);
                    return new JdbcTypeMirror<>(ctx, JdbcTypeKind.KAUMEI_JDBC_RESULT_SET, element, type, optional, cmp);
                } else if (declared.asElement() instanceof TypeElement te) {
                    if (ctx.JAVA_Boolean.isSameType(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.BOOLEAN, element, type, optional);
                    } else if (ctx.JAVA_Byte.isSameType(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.BYTE, element, type, optional);
                    } else if (ctx.JAVA_Character.isSameType(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.CHAR, element, type, optional);
                    } else if (ctx.JAVA_Double.isSameType(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.DOUBLE, element, type, optional);
                    } else if (ctx.JAVA_Float.isSameType(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.FLOAT, element, type, optional);
                    } else if (ctx.JAVA_Integer.isSameType(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.INT, element, type, optional);
                    } else if (ctx.JAVA_Long.isSameType(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.LONG, element, type, optional);
                    } else if (ctx.JAVA_Void.isSupertypeOf(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.VOID, element, type, optional);
                    } else if (ctx.KAUMEI_JDBC_JdbcBatch.isSupertypeOf(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.KAUMEI_JDBC_BATCH, element, type, optional);
                    } else if (ctx.JAVA_Short.isSameType(type)) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.SHORT, element, type, optional);
                    } else if (te.getKind() == ElementKind.RECORD) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.RECORD, element, type, optional);
                    } else if (te.getKind() == ElementKind.ENUM) {
                        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.ENUM, element, type, optional);
                    }
                }
                break;
        }
        return new JdbcTypeMirror<>(ctx, JdbcTypeKind.UNKNOWN, element, type, optional);
    }

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

    private final Context ctx;
    private final JdbcTypeKind kind;
    private final T element;
    private final TypeMirror type;
    private final OptionalFlag optional;
    private @Nullable TypeMirror cmpType;
    private @Nullable OptionalFlag cmpOptional;

    private JdbcTypeMirror(Context ctx, JdbcTypeKind kind, T element, TypeMirror type, OptionalFlag optional) {
        this.ctx = ctx;
        this.kind = kind;
        this.element = element;
        this.type = type;
        this.optional = optional;
    }

    private JdbcTypeMirror(Context ctx, JdbcTypeKind kind, T element,
                           TypeMirror type, OptionalFlag optional,
                           TypeOptional cmp) {
        this.ctx = ctx;
        this.kind = kind;
        this.element = element;
        this.type = type;
        this.optional = optional;
        this.cmpType = cmp.type();
        this.cmpOptional = cmp.optional();
    }

    @Override
    public String toString() {
        return "JdbcTypeMirror{" +
                "kind=" + kind +
                ", element=" + element +
                ", type=" + type +
                ", optional=" + optional +
                (cmpType != null ? ", cmpType=" + cmpType : "") +
                (cmpOptional != null ? ", cmpOptional=" + cmpOptional : "") +
                '}';
    }

    public JdbcTypeKind kind() {
        return this.kind;
    }

    public T element() {
        return this.element;
    }

    public TypeMirror asType() {
        if (this.element instanceof ExecutableElement ee) {
            return ee.getReturnType();
        } else if (this.element instanceof VariableElement ve) {
            return ve.asType();
        } else if (this.element instanceof RecordComponentElement rce) {
            return rce.asType();
        }
        throw new IllegalStateException("unknown: " + this.element);
    }

    public boolean isPrimitive() {
        return this.type.getKind().isPrimitive();
    }

    public Name javaSimpleName() {
        return element.getSimpleName();
    }

    // keep in mind: Optional<T> is treated different: T will be the type, you never ever see Optional<T> here
    public TypeMirror type() {
        return this.type;
    }

    public OptionalFlag optional() {
        return this.optional;
    }

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

    public boolean hasComponent() {
        return this.cmpType != null && this.cmpOptional != null;
    }

    public TypeMirror cmpType() {
        return Objects.requireNonNull(this.cmpType);
    }

    public OptionalFlag cmpOptional() {
        return Objects.requireNonNull(this.cmpOptional);
    }

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

    public TypeMirror cmpOrType() {
        return this.cmpType != null ? this.cmpType : type;
    }

    public OptionalFlag cmpOrOptional() {
        return this.cmpOptional != null ? this.cmpOptional : optional;
    }

}