JavaAnnoType.java

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

import io.kaumei.jdbc.anno.ctx.AnnoElementValueMap;
import io.kaumei.jdbc.anno.ctx.Context;
import io.kaumei.jdbc.anno.utils.SqlDV;
import io.kaumei.jdbc.annotation.JdbcUpdate;
import io.kaumei.jdbc.annotation.config.JdbcConfig;
import io.kaumei.jdbc.annotation.config.JdbcLogLevel;
import org.jspecify.annotations.Nullable;

import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import java.lang.annotation.Annotation;
import java.util.function.Function;

import static java.util.Objects.requireNonNull;

public final class JavaAnnoType<T> {

    private final Context ctx;
    private final TypeElement typeElement;
    private final Function<AnnoElementValueMap, T> func;
    private final Class<T> annoValueCls;

    public JavaAnnoType(Context ctx,
                        Class<? extends Annotation> annoCls,
                        Class<T> annoValueCls,
                        Function<AnnoElementValueMap, T> func
    ) {
        this.ctx = ctx;
        this.typeElement = ctx.elements.getTypeElement(annoCls.getCanonicalName());
        this.annoValueCls = annoValueCls;
        this.func = func;
    }

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

    @Override
    public String toString() {
        return typeElement.getQualifiedName().toString();
    }

    public TypeElement typeElement() {
        return this.typeElement;
    }

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

    public boolean hasAnno(AnnotatedConstruct element) {
        for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
            if (isSameType(mirror)) {
                return true;
            }
        }
        return false;
    }

    public T cast(Object value) {
        return this.annoValueCls.cast(value);
    }

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

    public T getAnno(AnnotationMirror mirror) {
        return requireNonNull(this.func.apply(new AnnoElementValueMap(mirror)));
    }

    public T getAnno(AnnotatedConstruct element) {
        var mirror = requireNonNull(findOne(element));
        return requireNonNull(this.func.apply(new AnnoElementValueMap(mirror)));
    }

    public T getAnno(AnnotatedConstruct element, T defaultValue) {
        var mirror = findOne(element);
        return mirror == null ? defaultValue : requireNonNull(this.func.apply(new AnnoElementValueMap(mirror)));
    }

    /**
     * @return null if element could not be found else the value
     */
    public @Nullable T getAnnoOpt(AnnotatedConstruct element) {
        var mirror = findOne(element);
        return mirror == null ? null : getAnno(mirror);
    }

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

    private boolean isSameType(AnnotationMirror mirror) {
        return ctx.isSameType(mirror.getAnnotationType(), typeElement.asType());
    }

    private @Nullable AnnotationMirror findOne(AnnotatedConstruct element) {
        AnnotationMirror found = null;
        for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
            if (isSameType(mirror)) {
                if (found == null) {
                    found = mirror;
                } else {
                    throw new IllegalStateException("Found too many annotations");
                }
            }
        }
        return found;
    }

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

    public record JdbcGenerationRecord(TypeMirror[] classAnnotations,
                                       TypeMirror[] constructorAnnotations) {
        public static JdbcGenerationRecord of(AnnoElementValueMap map) {
            return new JdbcGenerationRecord(
                    map.getTypeMirrorArray("classAnnotations"),
                    map.getTypeMirrorArray("constructorAnnotations"));
        }
    }

    public record JdbcDebugRecord() {
        public static JdbcDebugRecord of(AnnoElementValueMap map) {
            return new JdbcDebugRecord();
        }
    }

    public record JdbcNativeRecord(@Nullable TypeMirror cls, @Nullable String method) {
        public static JdbcNativeRecord of(AnnoElementValueMap map) {
            var cls = map.getTypeMirror("cls");
            var method = map.getString("method", "");
            return new JdbcNativeRecord(
                    cls == null || cls.getKind() == TypeKind.VOID ? null : cls,
                    method.isBlank() ? null : method);
        }
    }

    public record JdbcSelectRecord(SqlDV sql) {
        public static JdbcSelectRecord of(AnnoElementValueMap map) {
            return new JdbcSelectRecord(SqlDV.of(map.getString("value", "")));
        }
    }

    public record JdbcUpdateRecord(SqlDV sql, JdbcUpdate.GeneratedValues returnGeneratedValues) {
        public static JdbcUpdateRecord of(AnnoElementValueMap map) {
            return new JdbcUpdateRecord(
                    SqlDV.of(map.getString("value", "")),
                    map.getEnum("returnGeneratedValues", JdbcUpdate.GeneratedValues.class, JdbcUpdate.GeneratedValues.NONE));
        }
    }

    public record JdbcBatchUpdateRecord() {
        public static JdbcBatchUpdateRecord of(AnnoElementValueMap map) {
            return new JdbcBatchUpdateRecord();
        }
    }

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

    public record JdbcConfigRecord(@Nullable TypeMirror parent, TypeMirror[] converter,
                                   String generatedClassSuffix,
                                   JdbcConfig.GeneratedValues returnGeneratedValues,
                                   int maxCollectionPlaceholders,
                                   int maxTotalPlaceholders) {
        public static JdbcConfigRecord of(AnnoElementValueMap map) {
            var parent = map.getTypeMirror("parent");
            var converter = map.getTypeMirrorArray("converter");
            var generatedClassSuffix = map.getString("generatedClassSuffix", "Jdbc");
            var returnGeneratedValues = map.getEnum("returnGeneratedValues", JdbcConfig.GeneratedValues.class, JdbcConfig.GeneratedValues.DEFAULT);
            var maxCollectionPlaceholders = map.getInteger("maxCollectionPlaceholders", 1_000);
            var maxTotalPlaceholders = map.getInteger("maxTotalPlaceholders", 2_000);
            return new JdbcConfigRecord(
                    parent != null && parent.getKind() == TypeKind.VOID ? null : parent,
                    converter,
                    generatedClassSuffix,
                    returnGeneratedValues,
                    maxCollectionPlaceholders,
                    maxTotalPlaceholders);
        }
    }

    public record JdbcLogLevelRecord(JdbcLogLevel.LogLevel value) {
        public static JdbcLogLevelRecord of(AnnoElementValueMap map) {
            return new JdbcLogLevelRecord(map.getEnum("value", JdbcLogLevel.LogLevel.class, JdbcLogLevel.LogLevel.ERROR));
        }
    }

}