SQLNameDV.java

/*
 * SPDX-FileCopyrightText: 2026 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.msg.JdbcMsg;
import io.kaumei.jdbc.anno.msg.Msg;
import io.kaumei.jdbc.anno.utils.AnnoUtils;
import org.jspecify.annotations.Nullable;

public class SQLNameDV implements Msg.HasMessages {

    private static final SQLNameDV NO_NAME = new SQLNameDV(null, Msg.empty());

    public static SQLNameDV noName() {
        return NO_NAME;
    }

    public static SQLNameDV of(AnnoElementValueMap map) {
        return ofStaticValue(map.getString("value", ""));
    }

    public static SQLNameDV ofStaticValue(String value) {
        return new SQLNameDV(value, validate(value));
    }

    public static SQLNameDV ofJavaIdentifier(CharSequence javaName) {
        if (javaName.isEmpty()) {
            throw new IllegalArgumentException("name can not be empty");
        }

        var length = javaName.length();
        var sb = new StringBuilder(length);
        for (int i = 0; i < length; i++) {
            var current = javaName.charAt(i);
            if (i > 0 && Character.isUpperCase(current)
                    && (Character.isLowerCase(javaName.charAt(i - 1))
                    || Character.isDigit(javaName.charAt(i - 1))
                    || i + 1 < length && Character.isUpperCase(javaName.charAt(i - 1))
                    && Character.isLowerCase(javaName.charAt(i + 1)))) {
                sb.append('_');
            }
            sb.append(Character.toLowerCase(current));
        }
        var value = sb.toString();
        return new SQLNameDV(value, validate(value));
    }

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

    private final @Nullable String value;
    private final Msg.Messages messages;

    private SQLNameDV(@Nullable String value, Msg.Messages messages) {
        this.messages = messages;
        this.value = messages.hasMessages() ? null : value;
    }

    public boolean hasName() {
        return this.value != null;
    }

    public String value() {
        return AnnoUtils.requireState(this.value, "value");
    }

    @Override
    public boolean hasMessages() {
        return this.messages.hasMessages();
    }

    @Override
    public Msg.Messages messages() {
        return this.messages;
    }

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

    private static Msg.Messages validate(String value) {
        if (value.isBlank()) {
            return JdbcMsg.annotationValueMustNotBeBlank("@JdbcName");
        } else if (value.charAt(0) == '.' || value.charAt(value.length() - 1) == '.' || value.contains("..")) {
            return JdbcMsg.invalidJdbcName(value);
        }
        for (int i = 0; i < value.length(); i++) {
            if (isDenied(value.charAt(i))) {
                return JdbcMsg.invalidJdbcName(value);
            }
        }
        return Msg.empty();
    }

    private static boolean isDenied(char ch) {
        return Character.isWhitespace(ch)
                || Character.isISOControl(ch)
                || ch == '\''
                || ch == '"'
                || ch == '`'
                || ch == '['
                || ch == ']'
                || ch == ';'
                || ch == ','
                || ch == '('
                || ch == ')'
                || ch == '{'
                || ch == '}'
                || ch == '+'
                || ch == '-'
                || ch == '*'
                || ch == '/'
                || ch == '%'
                || ch == '='
                || ch == '<'
                || ch == '>'
                || ch == '!'
                || ch == '&'
                || ch == '|'
                || ch == '^'
                || ch == '~'
                || ch == '?'
                || ch == ':'
                || ch == '\\';
    }

}