JavaModelToString.java

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

import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.*;
import java.util.List;

public final class JavaModelToString {

    private JavaModelToString() {
    }

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

    public static String typeElement(TypeElement element) {
        var sb = new StringBuilder();
        typeElement(sb, element);
        return sb.toString();
    }

    private static void typeElement(StringBuilder sb, TypeElement element) {
        annotatedConstruct(sb, element);
        if (!sb.isEmpty()) {
            sb.append(' ');
        }
        sb.append(element.getQualifiedName());
    }

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

    public static String recordComponentElement(RecordComponentElement element) {
        var sb = new StringBuilder();
        annotatedConstruct(sb, element);
        if (!sb.isEmpty()) {
            sb.append(' ');
        }
        sb.append(typeMirror(element.asType()));
        sb.append(' ').append(element.getSimpleName());
        return sb.toString();
    }

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

    public static String variableElement(VariableElement element) {
        var sb = new StringBuilder();
        variableElement(sb, element);
        return sb.toString();
    }

    private static void variableElement(StringBuilder sb, VariableElement element) {
        annotatedConstruct(sb, element);
        if (!sb.isEmpty()) {
            sb.append(' ');
        }
        sb.append(typeMirror(element.asType()));
        sb.append(' ').append(element.getSimpleName());
    }

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

    public static String executableElement(ExecutableElement element) {
        var sb = new StringBuilder();
        executableElement(sb, element);
        return sb.toString();
    }

    private static void executableElement(StringBuilder sb, ExecutableElement method) {
        annotatedConstruct(sb, method);
        if (!sb.isEmpty()) {
            sb.append(' ');
        }
        for (var modifier : method.getModifiers()) {
            sb.append(modifier).append(' ');
        }
        if (!method.getTypeParameters().isEmpty()) {
            sb.append('<');
            for (int i = 0; i < method.getTypeParameters().size(); i++) {
                if (i > 0) {
                    sb.append(',');
                }
                sb.append(method.getTypeParameters().get(i));
            }
            sb.append("> ");
        }
        sb.append(typeMirror(method.getReturnType()));
        sb.append(' ').append(method.getSimpleName()).append('(');
        for (int i = 0; i < method.getParameters().size(); i++) {
            if (i > 0) {
                sb.append(',');
            }
            sb.append(variableElement(method.getParameters().get(i)));
        }
        sb.append(')');
    }

    private static void annotatedConstruct(StringBuilder sb, AnnotatedConstruct tm) {
        boolean space = false;
        for (var a : tm.getAnnotationMirrors()) {
            if (space) {
                sb.append(' ');
            } else {
                space = true;
            }
            sb.append(a);
        }
    }

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

    public static String typeMirror(TypeMirror tm) {
        return tm.toString();
    }

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

    public static String searchKey(TypeMirror tm) {
        var sb = new StringBuilder();
        searchKey(sb, tm);
        return sb.toString();
    }

    private static void searchKey(StringBuilder sb, TypeMirror tm) {
        switch (tm.getKind()) {
            case DECLARED:
                searchKeyDeclaredType(sb, (DeclaredType) tm);
                break;
            case TYPEVAR:
                sb.append(((TypeVariable) tm).asElement().getSimpleName());
                break;
            case ARRAY:
                searchKey(sb, ((ArrayType) tm).getComponentType());
                sb.append("[]");
                break;
            case WILDCARD:
                searchKeyWildcard(sb, (WildcardType) tm);
                break;
            default:
                sb.append(tm.getKind().name().toLowerCase()); // primitives, void etc.
        }
    }

    private static void searchKeyDeclaredType(StringBuilder sb, DeclaredType dt) {
        TypeElement type = (TypeElement) dt.asElement();
        sb.append(type.getQualifiedName());
        List<? extends TypeMirror> args = dt.getTypeArguments();
        if (!args.isEmpty()) {
            sb.append('<');
            for (int i = 0; i < args.size(); i++) {
                if (i > 0) {
                    sb.append(",");
                }
                searchKey(sb, args.get(i));
            }
            sb.append('>');
        }
    }

    private static void searchKeyWildcard(StringBuilder sb, WildcardType wt) {
        if (wt.getExtendsBound() != null) {
            sb.append("? extends ");
            searchKey(sb, wt.getExtendsBound());
        } else if (wt.getSuperBound() != null) {
            sb.append("? super ");
            searchKey(sb, wt.getSuperBound());
        } else {
            sb.append('?');
        }
    }

}