JdbcMsg.java

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

import io.kaumei.jdbc.anno.model.JavaAnnoType;
import io.kaumei.jdbc.anno.model.JdbcTypeMirror;
import io.kaumei.jdbc.anno.model.OptionalFlag;
import io.kaumei.jdbc.anno.store.SearchKey;
import io.kaumei.jdbc.anno.store.SourceDV;
import io.kaumei.jdbc.anno.store.StoreID;

import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import static io.kaumei.jdbc.anno.ctx.JavaModelToString.searchKey;

public class JdbcMsg {

    private JdbcMsg() { // JaCoCo:no
    }

    public static final Msg.Message INVALID_DEPENDENT_CONVERTER = Msg.of("invalid dependent converter");
    public static final Msg.Message DUPLICATE_KEY = Msg.of("duplicate key");

    public static Msg.Message duplicateConverter(SourceDV source) {
        return Msg.of("Duplicate converter: " + source);
    }

    public static final Msg.Message DUPLICATE_METHOD = Msg.of("duplicate method");
    public static final Msg.Message INCOMPATIBLE_TYPE = Msg.of("incompatible type");
    public static final Msg.Message NOT_FOUND = Msg.of("not found");
    public static final Msg.Message OPTIONAL_TYPE_NOT_SUPPORTED = Msg.of("""
            Optional<T> type not supported.
            See JavaDoc of Optional:
            Optional is primarily intended for use as a method return ...
            """);
    public static final Msg.Message INVALID_RETURN_TYPE = Msg.of("Invalid return type");
    public static final Msg.Message NULLABLE_NOT_SUPPORTED = Msg.of("@Nullable not supported");
    public static final Msg.Message JDBC_CONVERTER_NAME_NOT_SUPPORTED = Msg.of("@JdbcConverterName not supported");
    public static final Msg.Message JAVA_TO_JDBC_RESOLVER_LIST_REQUIRES_COMPONENT_TYPE = Msg.of("List parameters used with values placeholders must define a component type, e.g. List<String>");
    public static final Msg.Message JAVA_TO_JDBC_RESOLVER_VALUES_UNSUPPORTED_TYPES = Msg.of("Only Array, List or Record are supported");
    public static final Msg.Message JAVA_TO_JDBC_RESOLVER_NAMES_UNSUPPORTED_TYPES = Msg.of("Only records are supported");
    public static final Msg.Message JDBC_TO_JAVA_METHOD_MUST_BE_STATIC = Msg.of("@JdbcToJava method must be static");
    public static final Msg.Message JDBC_TO_JAVA_METHOD_MUST_BE_VISIBLE = Msg.of("@JdbcToJava method must be visible (public/package)");
    public static final Msg.Message JDBC_TO_JAVA_METHOD_THROWS_INCOMPATIBLE_EXCEPTIONS = Msg.of("@JdbcToJava method throws incompatible exceptions");
    public static final Msg.Message JDBC_TO_JAVA_MUST_NOT_RETURN_VOID = Msg.of("@JdbcToJava must not return void");
    public static final Msg.Message JDBC_TO_JAVA_METHOD_REQUIRES_PARAMETER = Msg.of("@JdbcToJava method must have at least one parameter");
    public static final Msg.Message JDBC_TO_JAVA_RECORD_CONSTRUCTOR_DOES_NOT_SUPPORT_RESULT_SET = Msg.of("@JdbcToJava record constructor does not support ResultSet");
    public static final Msg.Message JDBC_TO_JAVA_CLASS_CONSTRUCTOR_DOES_NOT_SUPPORT_RESULT_SET_INT = Msg.of("@JdbcToJava class constructor does not support ResultSet,int");
    public static final Msg.Message JDBC_TO_JAVA_FIRST_PARAMETER_MUST_BE_RESULT_SET = Msg.of("@JdbcToJava first parameter must be ResultSet");
    public static final Msg.Message JDBC_TO_JAVA_RESULT_SET_PARAMETER_COUNT_INVALID = Msg.of("@JdbcToJava with first ResultSet param has to many parameters");
    public static final Msg.Message JDBC_TO_JAVA_NAME_MAPPING_UNSUPPORTED_FOR_ONE_PARAM = Msg.of("@JdbcToJava name mapping not supported for one param");
    public static final Msg.Message JDBC_TO_JAVA_NAME_MAPPING_UNSUPPORTED_FOR_RESULT_SET = Msg.of("@JdbcToJava name mapping not supported for ResultSet");
    public static final Msg.Message JDBC_TO_JAVA_NULLABLE_PARAMETER_UNSUPPORTED = Msg.of("@JdbcToJava nullable param not supported");
    public static final Msg.Message JDBC_TO_JAVA_RETURN_TYPE_MUST_BE_NON_NULL_OR_UNSPECIFIED = Msg.of("@JdbcToJava method return type must be @NonNull or unspecified");
    public static final Msg.Message JDBC_TO_JAVA_FIRST_PARAMETER_MUST_BE_NON_NULL_OR_UNSPECIFIED = Msg.of("@JdbcToJava method first parameter must be @NonNull or unspecified");
    public static final Msg.Message JDBC_TO_JAVA_SECOND_PARAMETER_MUST_BE_INT = Msg.of("@JdbcToJava method second parameter must be int");
    public static final Msg.Message JDBC_TO_JAVA_RETURN_TYPE_MUST_BE_NULLABLE_OR_UNSPECIFIED = Msg.of("@JdbcToJava method return type must be a @Nullable or unspecified");
    public static final Msg.Message JDBC_TO_JAVA_ANNOTATION_NAME_UNSUPPORTED = Msg.of("Annotation must not have a name");
    public static final Msg.Message JDBC_TO_JAVA_TOO_MANY_ANNOTATIONS = Msg.of("To many annotations");
    public static final Msg.Message JDBC_TO_JAVA_NO_CONSTRUCTOR_FOUND = Msg.of("no constructor found");
    public static final Msg.Message JDBC_TO_JAVA_TOO_MANY_CONSTRUCTORS = Msg.of("too many constructors were found");
    public static final Msg.Message JDBC_TO_JAVA_COLUMN_OBJECT_REQUIRES_COLUMN_CONVERTER = Msg.of("@JdbcToJava column object requires a column converter");
    public static final Msg.Message JDBC_TO_JAVA_ROW_COMPONENT_REQUIRES_COLUMN_CONVERTER = Msg.of("@JdbcToJava row component requires a column converter");
    public static final Msg.Message JAVA_TO_JDBC_METHOD_REQUIRES_ONE_OR_THREE_PARAMETERS = Msg.of("@JavaToJdbc method must have one or three parameters");
    public static final Msg.Message JAVA_TO_JDBC_METHOD_MUST_BE_VISIBLE = Msg.of("@JavaToJdbc Must be visible (public/package)");
    public static final Msg.Message JAVA_TO_JDBC_RETURN_TYPE_MUST_BE_NON_NULL_OR_UNSPECIFIED = Msg.of("@JavaToJdbc return type must be @NonNull or unspecified");
    public static final Msg.Message JAVA_TO_JDBC_METHOD_MUST_HAVE_NO_PARAMETERS = Msg.of("@JavaToJdbc method must have no parameters");
    public static final Msg.Message JAVA_TO_JDBC_HAS_INCOMPATIBLE_EXCEPTIONS = Msg.of("has incompatible exceptions");
    public static final Msg.Message JAVA_TO_JDBC_RECORD_MUST_HAVE_ONE_COMPONENT = Msg.of("Record must have exact one component");
    public static final Msg.Message JAVA_TO_JDBC_RECORD_COMPONENT_MUST_BE_NON_NULL_OR_UNSPECIFIED = Msg.of("Record component must be 'non-null' or 'unspecified'");
    public static final Msg.Message JAVA_TO_JDBC_NO_TYPE = Msg.of("no type");
    public static final Msg.Message JAVA_TO_JDBC_FIRST_PARAMETER_MUST_BE_PREPARED_STATEMENT = Msg.of("first parameter must be a PreparedStatement");
    public static final Msg.Message JAVA_TO_JDBC_FIRST_PARAMETER_MUST_BE_NON_NULL_OR_UNSPECIFIED = Msg.of("first parameter must be @NonNull or unspecified");
    public static final Msg.Message JAVA_TO_JDBC_SECOND_PARAMETER_MUST_BE_INT = Msg.of("second parameter must be int");
    public static final Msg.Message JAVA_TO_JDBC_THIRD_PARAMETER_MUST_BE_NULLABLE_OR_UNSPECIFIED = Msg.of("third parameter must be @Nullable or unspecified");
    public static final Msg.Message JAVA_TO_JDBC_ANNOTATION_NAME_UNSUPPORTED = Msg.of("Annotation must not have a name");
    public static final Msg.Message JAVA_TO_JDBC_TOO_MANY_ANNOTATIONS = Msg.of("To many annotations");

    public static Msg.Message jdbcToJavaParameterOptionalInvalid(CharSequence parameter) {
        return Msg.of("@JdbcToJava parameter " + parameter + " invalid Optional");
    }

    public static Msg.Message jdbcToJavaElementTypeNotFound(TypeMirror type) {
        return Msg.of("@JavaToJdbc element type not found for " + type);
    }

    public static Msg.Message jdbcToJavaAnnotationHasWrongType(TypeMirror type) {
        return Msg.of("Annotation has wrong type: " + type);
    }

    public static Msg.Message javaToJdbcElementTypeNotFound(TypeMirror type) {
        return Msg.of("@JavaToJdbc element type not found for " + type);
    }

    public static Msg.Message javaToJdbcAnnotationHasWrongType(TypeMirror type) {
        return Msg.of("Annotation has wrong type: " + type);
    }

    public static Msg.Message annotationValueMustNotBeBlank(CharSequence annotation) {
        return Msg.of(annotation + " value must not be blank");
    }

    public static Msg.Message invalidJdbcName(CharSequence name) {
        return Msg.of("@JdbcName value '" + name + "' is not a valid SQL name");
    }

    public static Msg.Message converter(String text) {
        return Msg.of(text);
    }

    public static Msg.Message cycle(List<?> cycle) {
        return Msg.of("generate cycle: " + cycle);
    }

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

    public static Msg.Message ambiguous(Set<StoreID> ambiguous) {
        return Msg.of("Ambiguous converter: " + toSortedList(ambiguous, StoreID::toString));
    }

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

    protected static Msg.Message invalidParam(CharSequence name, String type, Msg.Messages messages) {
        var sb = new StringBuilder();
        sb.append("Java method parameter '").append(name);
        sb.append("': type '").append(type);
        sb.append("' must be supported as SQL placeholder, but ");
        messages.join(sb, ", ");
        return Msg.of(sb.toString());
    }

    public static Msg.Message invalidParam(VariableElement param, Msg.Messages messages) {
        return invalidParam(param.getSimpleName(), searchKey(param.asType()), messages);
    }

    public static Msg.Message jdbcMethodRequiresEntryPointAnnotation() {
        return Msg.of("Java method: must have one of @JdbcNative, @JdbcSelect, @JdbcUpdate or @JdbcBatchUpdate");
    }

    public static Msg.Message jdbcMethodRequiresSingleEntryPointAnnotation() {
        return Msg.of("Java method: must not have multiple entry-point annotations");
    }

    public static Msg.Message jdbcSelectRequiresSqlString() {
        return Msg.of("Java method: @JdbcSelect requires a SQL string");
    }

    public static Msg.Message jdbcUpdateRequiresSqlString() {
        return Msg.of("Java method: @JdbcUpdate requires a SQL string");
    }

    public static Msg.Message javaMethodHasUnusedAnnotations(Set<JavaAnnoType<?>> annotations) {
        return Msg.of("Java method: has unused annotations: " + toSortedList(annotations, JavaAnnoType::toString));
    }

    public static Msg.Message javaMethodHasUnusedAnnotations(Class<?>... annotations) {
        return Msg.of("Java method: has unused annotations: " + Arrays.stream(annotations).map(Class::getCanonicalName).toList());
    }

    public static Msg.Message unusedAnnotations(TypeElement element, Set<JavaAnnoType<?>> annoSet) {
        return Msg.of("Type " + element.getQualifiedName() + " has unused annotations: " + toSortedList(annoSet, JavaAnnoType::toString));
    }

    public static Msg.Message javaMethodParameterHasUnusedAnnotations(CharSequence parameter, Set<JavaAnnoType<?>> annotations) {
        return Msg.of("Java method parameter '" + parameter + "': has unused annotations: " + toSortedList(annotations, JavaAnnoType::toString));
    }

    private static <T> String toSortedList(Set<T> annotations, Function<T, String> func) {
        return annotations.stream().map(func).sorted().toList().toString();
    }

    public static Msg.Message javaMethodParameterJdbcConverterNameUnsupportedForSqlPlaceholder(CharSequence parameter,
                                                                                               CharSequence placeholder,
                                                                                               CharSequence shape) {
        return Msg.of("Java method parameter '" + parameter + "': @JdbcConverterName is not supported for SQL "
                + shape + " placeholder '" + placeholder + "'");
    }

    public static Msg.Message sqlPlaceholderUnnamedUnsupported() {
        return Msg.of("SQL placeholder: unnamed placeholders are not supported");
    }

    public static Msg.Message sqlPlaceholderPathUnsupported(CharSequence placeholder) {
        return Msg.of("SQL placeholder '" + placeholder + "': paths are not supported");
    }

    public static Msg.Message sqlPlaceholderNotFound(CharSequence placeholder) {
        return Msg.of("SQL placeholder '" + placeholder + "': not found in method parameter list");
    }

    public static Msg.Message sqlPlaceholderUsedWithDifferentShapes(CharSequence placeholder,
                                                                    CharSequence first,
                                                                    CharSequence second) {
        return Msg.of("SQL placeholder '" + placeholder + "': must not be used as '" + first
                + "' and '" + second + "' placeholder");
    }

    public static Msg.Message sqlPlaceholderCountExceedsLimit(int count, int limit) {
        return Msg.of("SQL placeholder count exceeds @JdbcConfig.maxTotalPlaceholders: " + count + " > " + limit);
    }

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

    public static Msg.Message invalidSqlParameterConverter(String sql, SourceDV component, Msg.Messages messages) {
        return new InvalidParamConverter(sql, component, messages);
    }

    public static Msg.Message invalidJdbcToJavaResultConverter(SourceDV method,
                                                               SearchKey searchKey,
                                                               Msg.Messages messages) {
        return new InvalidReturnConverter(method, searchKey, messages);
    }

    private static String renderInvalidSqlParameterConverter(String sql, SourceDV component, Msg.Messages messages) {
        var sb = new StringBuilder();
        sb.append("sql......: ':").append(sql);
        sb.append("'\nparameter: ").append(component);
        sb.append('\n');
        appendMessages(sb, messages);
        return sb.toString();
    }

    private static String renderInvalidJdbcToJavaResultConverter(SearchKey searchKey,
                                                                 Msg.Messages messages) {
        var sb = new StringBuilder();
        sb.append("return...: ").append(searchKey.toStoreId()).append('\n');
        appendMessages(sb, messages);
        return sb.toString();
    }

    private static void appendMessages(StringBuilder sb, Msg.Messages messages) {
        var first = true;
        for (var message : messages) {
            if (first) {
                first = false;
            } else {
                sb.append('\n');
            }
            sb.append(message.text());
        }
    }

    // @JdbcNative ------------------------------------------------------------

    public static Msg.Message jdbcNativeRequiresMatchingStaticMethod(TypeElement targetClass,
                                                                     CharSequence targetMethod) {
        return jdbcNativeRequiresMatchingStaticMethod(targetClass.toString(), targetMethod);
    }

    public static Msg.Message jdbcNativeRequiresMatchingStaticMethod(CharSequence targetClass,
                                                                     CharSequence targetMethod) {
        return Msg.of("Java method: @JdbcNative requires a matching static method '" + targetMethod
                + "' in '" + targetClass + "' with java.sql.Connection as first parameter");
    }

    public static Msg.Message jdbcNativeRequiresCallableStaticMethod(TypeElement targetClass,
                                                                     CharSequence targetMethod) {
        return jdbcNativeRequiresCallableStaticMethod(targetClass.toString(), targetMethod);
    }

    public static Msg.Message jdbcNativeRequiresCallableStaticMethod(CharSequence targetClass,
                                                                     CharSequence targetMethod) {
        return Msg.of("Java method: @JdbcNative requires static method '" + targetMethod + "' in '"
                + targetClass + "' to match return type, parameters, nullness and checked exceptions");
    }

    public static Msg.Message returnTypeNotAssignable(CharSequence targetReturnType,
                                                      CharSequence sourceReturnType) {
        return Msg.of("Return type '" + targetReturnType + "' is not assignable to '" + sourceReturnType + "'");
    }

    public static Msg.Message returnTypeNullnessMismatch(OptionalFlag targetFlag,
                                                         OptionalFlag sourceFlag) {
        return Msg.of("Return type mismatch. target: '" + targetFlag + "' is not compatible with source: '" + sourceFlag + "'");
    }

    public static Msg.Message javaMethodsHaveDifferentParameter() {
        return Msg.of("have different parameter");
    }

    public static Msg.Message firstParameterMustBeConnection() {
        return Msg.of("first param must have type 'java.sql.Connection'");
    }

    public static Msg.Message sourceParamOptionalTypeNotSupported(int pos) {
        return Msg.of("Source param at pos " + pos + ": " + OPTIONAL_TYPE_NOT_SUPPORTED);
    }

    public static Msg.Message targetParamOptionalTypeNotSupported(int pos) {
        return Msg.of("Target param at pos " + pos + ": " + OPTIONAL_TYPE_NOT_SUPPORTED);
    }

    public static Msg.Message paramNullnessMismatch(int pos,
                                                    OptionalFlag sourceFlag,
                                                    OptionalFlag targetFlag) {
        return Msg.of("Param mismatch optional at pos " + pos + ": '" + sourceFlag + "' is not compatible with '" + targetFlag + "'");
    }

    public static Msg.Message paramTypeMismatch(int pos,
                                                CharSequence sourceType,
                                                CharSequence targetType) {
        return Msg.of("Param mismatch type at pos " + pos + ": '" + sourceType + "' is not same type '" + targetType + "'");
    }

    public static Msg.Message exceptionNotCompatible(CharSequence exception) {
        return Msg.of("Exception not compatible: " + exception);
    }

    // @JdbcSelect ------------------------------------------------------------

    public static Msg.Message jdbcSelectRequiresSupportedReturnType(JdbcTypeMirror<?> returnType) {
        return jdbcSelectRequiresSupportedReturnType(returnType.asType().toString());
    }

    public static Msg.Message jdbcSelectRequiresSupportedReturnType(CharSequence returnType) {
        return Msg.of("Java method return value: @JdbcSelect requires a supported value, row, list, stream, "
                + "JdbcIterable, JdbcResultSet, or JdbcRow return type, but found '"
                + returnType + "'");
    }

    public static Msg.Message jdbcSelectJdbcRowRequiresNullableOrOptional(OptionalFlag optional) {
        return Msg.of("Java method return value: @JdbcSelect with JdbcRow<T> requires a nullable or "
                + "Optional<JdbcRow<T>> return type, but found '" + optional + "'");
    }

    public static Msg.Message jdbcSelectNoRowsReturnNullRequiresNullableOrOptional(OptionalFlag optional) {
        return Msg.of("Java method return value: @JdbcNoRows(RETURN_NULL) requires a nullable or Optional<T> "
                + "return type, but found '" + optional + "'");
    }

    public static Msg.Message jdbcSelectNoRowsReturnNullRequiresNullable(OptionalFlag optional) {
        return Msg.of("Java method return value: @JdbcNoRows(RETURN_NULL) requires a nullable return type, "
                + "but found '" + optional + "'");
    }

    public static Msg.Message jdbcSelectRequiresNonNullOrUnspecifiedReturnType(OptionalFlag optional) {
        return Msg.of("Java method return value: @JdbcSelect requires a non-null or unspecified return type "
                + "for list, stream, JdbcIterable, or JdbcResultSet return types, but found '" + optional + "'");
    }

    public static Msg.Message jdbcSelectRequiresNonNullOrUnspecifiedRowReturnComponent(OptionalFlag optional) {
        return Msg.of("Java method return value: row return components must be unspecified or non-null, "
                + "but found '" + optional + "'");
    }

    public static final Msg.Message JDBC_SELECT_REQUIRES_RESULT_SET_TYPE_AND_CONCURRENCY = Msg.of(
            "Java method: @JdbcResultSetType and @JdbcResultSetConcurrency must be configured together");

    // @JdbcUpdate ------------------------------------------------------------

    public static Msg.Message jdbcUpdateRequiresSupportedReturnType(JdbcTypeMirror<?> returnType) {
        return jdbcUpdateRequiresSupportedReturnType(returnType.asType().toString());
    }

    public static Msg.Message jdbcUpdateRequiresSupportedReturnType(CharSequence returnType) {
        return Msg.of("Java method return value: @JdbcUpdate requires void, int, boolean, or generated values "
                + "with a supported return type, but found '" + returnType + "'");
    }

    public static Msg.Message jdbcUpdateGeneratedValuesRequiresSupportedReturnType(JdbcTypeMirror<?> returnType) {
        return jdbcUpdateGeneratedValuesRequiresSupportedReturnType(returnType.asType().toString());
    }

    public static Msg.Message jdbcUpdateGeneratedValuesRequiresSupportedReturnType(CharSequence returnType) {
        return Msg.of("Java method return value: @JdbcUpdate generated values require a supported value or row "
                + "return type, but found '" + returnType + "'");
    }

    public static Msg.Message jdbcUpdateGeneratedValuesRequiresNonNullOrUnspecifiedReturnType(OptionalFlag optional) {
        return Msg.of("Java method return value: @JdbcUpdate generated values require a non-null or unspecified "
                + "return type, but found '" + optional + "'");
    }

    // @JdbcBatch ------------------------------------------------------------


    public static Msg.Message jdbcBatchRequiresNonNullOrUnspecifiedReturnType(OptionalFlag optional) {
        return Msg.of("Java method return value: @JdbcBatchUpdate requires a non-null or unspecified batch "
                + "interface return type, but found '" + optional + "'");
    }

    public static Msg.Message jdbcBatchRequiresBatchInterfaceReturnType(JdbcTypeMirror<?> returnType) {
        return jdbcBatchRequiresBatchInterfaceReturnType(returnType.asType().toString());
    }

    public static Msg.Message jdbcBatchRequiresBatchInterfaceReturnType(CharSequence returnType) {
        return Msg.of("Java method return value: @JdbcBatchUpdate requires a nested batch interface that "
                + "extends JdbcBatch, but found '" + returnType + "'");
    }

    public static Msg.Message jdbcBatchTypeMustBeInterfaceMessage(CharSequence batchType) {
        return Msg.of("Java method return value: @JdbcBatchUpdate requires a batch interface, but found '"
                + batchType + "'");
    }

    public static Msg.Message jdbcBatchTypeMustBeNestedInFactoryInterface(CharSequence batchType) {
        return Msg.of("Java method return value: @JdbcBatchUpdate requires the batch interface to be declared "
                + "inside the factory interface, but found '" + batchType + "'");
    }

    public static Msg.Message jdbcBatchTypeMustDeclareExactlyOneUpdateMethod(CharSequence batchType) {
        return Msg.of("Java method return value: @JdbcBatchUpdate batch interface '" + batchType
                + "' must declare exactly one non-static, non-default @JdbcUpdate method");
    }

    public static Msg.Message jdbcBatchUpdateMethodMustReturnVoidMessage(CharSequence batchType,
                                                                         CharSequence updateMethod) {
        return Msg.of("Java method: @JdbcBatchUpdate update method '" + batchType + "." + updateMethod
                + "' must return void");
    }

    public static Msg.Message jdbcBatchUpdateMethodMustNotUseDynamicCollectionExpansion(VariableElement param) {
        var sb = new StringBuilder();
        sb.append("Java method parameter '").append(param.getSimpleName());
        sb.append("': type '").append(searchKey(param.asType()));
        sb.append("' dynamic parameter extension in batch mode not allowed");
        return Msg.of(sb.toString());
    }

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

    public static final class InvalidParamConverter extends MessageImpl {

        private final String sql;
        private final SourceDV parameter;
        private final Msg.Messages messages;

        InvalidParamConverter(String sql, SourceDV parameter, Msg.Messages messages) {
            super(renderInvalidSqlParameterConverter(sql, parameter, messages));
            this.sql = sql;
            this.parameter = parameter;
            this.messages = messages;
        }

        public String sql() {
            return sql;
        }

        public SourceDV parameter() {
            return parameter;
        }

        public Msg.Messages messages() {
            return messages;
        }
    }

    public static final class InvalidReturnConverter extends MessageImpl {

        private final SourceDV method;
        private final SearchKey searchKey;
        private final Msg.Messages messages;

        InvalidReturnConverter(SourceDV method, SearchKey searchKey, Msg.Messages messages) {
            super(renderInvalidJdbcToJavaResultConverter(searchKey, messages));
            this.method = method;
            this.searchKey = searchKey;
            this.messages = messages;
        }

        public SourceDV method() {
            return method;
        }

        public SearchKey searchKey() {
            return searchKey;
        }

        public Msg.Messages messages() {
            return messages;
        }
    }

}