SourceMethod.java
/*
* SPDX-FileCopyrightText: 2025 kaumei.io
* SPDX-License-Identifier: Apache-2.0
*/
package io.kaumei.jdbc.anno.model;
import io.kaumei.jdbc.anno.ctx.ConfigService;
import io.kaumei.jdbc.anno.ctx.Context;
import io.kaumei.jdbc.anno.msg.JdbcMsg;
import io.kaumei.jdbc.anno.msg.Msg;
import io.kaumei.jdbc.annotation.JdbcUpdate;
import io.kaumei.jdbc.annotation.config.*;
import org.jspecify.annotations.Nullable;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
public abstract sealed class SourceMethod permits
SourceMethod.Select, SourceMethod.Update, SourceMethod.BatchUpdate,
SourceMethod.Native, SourceMethod.CallMethod,
SourceMethod.InvalidMethod {
public static SourceMethod of(Context ctx, ExecutableElement method) {
var messages = Msg.builder();
var annoMap = AnnoMap.of(ctx, method);
// ----- check entry points
var entryPoint = annoMap.entryPoints();
if (entryPoint.count() == 0) {
messages.add(JdbcMsg.jdbcMethodRequiresEntryPointAnnotation());
return InvalidMethod.of(ctx, method, annoMap, messages);
} else if (entryPoint.count() > 1) {
messages.add(JdbcMsg.jdbcMethodRequiresSingleEntryPointAnnotation());
return InvalidMethod.of(ctx, method, annoMap, messages);
}
var jdbcName = ctx.JDBC_NAME.getAnnoOpt(method);
messages.add(jdbcName);
var jdbcConverterName = ctx.JDBC_CONVERTER_NAME.getAnnoOpt(method);
if (jdbcConverterName != null && jdbcConverterName.isBlankName()) {
messages.add(JdbcMsg.annotationValueMustNotBeBlank("@JdbcConverterName"));
}
if (entryPoint.batchUpdate() != null) {
var parameter = SourceMethodParameter.of(ctx, messages, method);
return new BatchUpdate(ctx, method, annoMap, parameter, messages.build());
} else if (entryPoint.call() != null) {
SourceMethodParameter parameter;
if (entryPoint.call().sql().value().isBlank()) {
messages.add(JdbcMsg.jdbcCallRequiresSqlString());
parameter = SourceMethodParameter.of(ctx, messages, method);
} else {
parameter = SourceMethodParameter.of(ctx, messages, method, entryPoint.call().sql());
}
var returnType = ctx.jdbcTypeMirror(method);
if (returnType.kind() == JdbcTypeKind.RECORD
&& !returnType.optional().isNonNullOrUnspecified()
&& ctx.asElementOpt(returnType.type()) instanceof TypeElement record
&& record.getRecordComponents().size() > 1) {
messages.add(JdbcMsg.jdbcCallMultipleOutputsRequireNonNullReturnType());
}
return new CallMethod(ctx, method, annoMap, parameter, jdbcName, jdbcConverterName, messages.build());
} else if (entryPoint.nativeJdbc() != null) {
var parameter = SourceMethodParameter.of(ctx, messages, method);
return new Native(ctx, method, annoMap, parameter, entryPoint.nativeJdbc(), messages.build());
} else if (entryPoint.select() != null) {
SourceMethodParameter parameter;
if (entryPoint.select().sql().value().isBlank()) {
messages.add(JdbcMsg.jdbcSelectRequiresSqlString());
parameter = SourceMethodParameter.of(ctx, messages, method);
} else {
parameter = SourceMethodParameter.of(ctx, messages, method, entryPoint.select().sql());
parameter.selectUpdateValidation(messages);
}
return new Select(ctx, method, annoMap, parameter, messages.build());
} else if (entryPoint.update() != null) {
SourceMethodParameter parameter;
if (entryPoint.update().sql().value().isBlank()) {
messages.add(JdbcMsg.jdbcUpdateRequiresSqlString());
parameter = SourceMethodParameter.of(ctx, messages, method);
} else {
parameter = SourceMethodParameter.of(ctx, messages, method, entryPoint.update().sql());
parameter.selectUpdateValidation(messages);
}
return new Update(ctx, method, annoMap, parameter, entryPoint.update(), messages.build());
}
throw new IllegalStateException("Illegal state: entry point is not supported yet: " + entryPoint);
}
// ------------------------------------------------------------------------
protected final Context ctx;
protected final ExecutableElement method;
protected final AnnoMap annoMap;
protected final Msg.Messages messages;
protected final SourceMethodParameter parameter;
protected SourceMethod(Context ctx,
ExecutableElement method,
AnnoMap annoMap, SourceMethodParameter parameter,
Msg.Messages messages) {
this.ctx = ctx;
this.method = method;
this.annoMap = annoMap;
this.parameter = parameter;
this.messages = messages;
}
public ExecutableElement method() {
return method;
}
public AnnoMap annoMap() {
return annoMap;
}
public TypeElement parent() {
return (TypeElement) this.method.getEnclosingElement();
}
public SourceMethodParameter parameter() {
return parameter;
}
public boolean hasMessages() {
return messages.hasMessages();
}
public Msg.Messages messages() {
return messages;
}
public SQLNameDV jdbcName() {
return annoMap.get(ctx.JDBC_NAME, SQLNameDV.noName());
}
public ConverterNameDV jdbcConverterName() {
return annoMap.get(ctx.JDBC_CONVERTER_NAME, ConverterNameDV.unnamed());
}
public JdbcNoMoreRows.Kind jdbcNoMoreRows() {
return this.ctx.kaumeiConfig.searchOnMethod(this.ctx.kaumeiConfig.NO_MORE_ROWS, this);
}
public JdbcNoRows.Kind jdbcNoRows() {
return this.ctx.kaumeiConfig.searchOnMethod(this.ctx.kaumeiConfig.NO_ROWS, this);
}
public ConfigService.@Nullable OptionValue<Integer> jdbcFetchSize() {
return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.FETCH_SIZE, this);
}
public ConfigService.@Nullable OptionValue<Integer> jdbcMaxRows() {
return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.MAX_ROWS, this);
}
public ConfigService.@Nullable OptionValue<JdbcResultSetConcurrency.Kind> jdbcResultSetConcurrency() {
return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.RESULT_SET_CONCURRENCY, this);
}
public ConfigService.@Nullable OptionValue<JdbcResultSetType.Kind> jdbcResultSetType() {
return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.RESULT_SET_TYPE, this);
}
public ConfigService.@Nullable OptionValue<JdbcFetchDirection.Kind> jdbcFetchDirection() {
return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.FETCH_DIRECTION, this);
}
public ConfigService.@Nullable OptionValue<Integer> jdbcQueryTimeout() {
return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.QUERY_TIMEOUT, this);
}
public ConfigService.@Nullable OptionValue<Integer> jdbcBatchSize() {
return this.ctx.kaumeiConfig.search(this.ctx.kaumeiConfig.BATCH_SIZE, this);
}
// ------------------------------------------------------------------------
public Msg.Messages unusedAnno() {
var messages = Msg.builder();
if (annoMap.hasUnused()) {
messages.add(JdbcMsg.javaMethodHasUnusedAnnotations(annoMap.unused()));
}
parameter.addUnusedAnno(messages);
return messages.build();
}
// ------------------------------------------------------------------------
public JdbcTypeMirror<ExecutableElement> returnType() {
return ctx.jdbcTypeMirror(this.method);
}
// ------------------------------------------------------------------------
public static final class Select extends SourceMethod {
Select(Context ctx, ExecutableElement method, AnnoMap annoMap, SourceMethodParameter parameter,
Msg.Messages messages) {
super(ctx, method, annoMap, parameter, messages);
}
@Override
public String toString() {
return "@JdbcSelect";
}
}
// ------------------------------------------------------------------------
public static final class Update extends SourceMethod {
private final JavaAnnoType.JdbcUpdateRecord annotation;
Update(Context ctx, ExecutableElement method, AnnoMap annoMap, SourceMethodParameter parameter,
JavaAnnoType.JdbcUpdateRecord annotation, Msg.Messages messages) {
super(ctx, method, annoMap, parameter, messages);
this.annotation = annotation;
}
public JdbcUpdate.GeneratedValues jdbcReturnGeneratedValues() {
var value = this.annotation.returnGeneratedValues();
if (value == JdbcUpdate.GeneratedValues.DEFAULT) {
value = this.ctx.kaumeiConfig.jdbcReturnGeneratedValues();
}
return value;
}
public String[] returnGeneratedColumns() {
return this.annotation.returnGeneratedColumns();
}
@Override
public String toString() {
return "@JdbcUpdate";
}
}
// ------------------------------------------------------------------------
public static final class BatchUpdate extends SourceMethod {
BatchUpdate(Context ctx, ExecutableElement method, AnnoMap annoMap, SourceMethodParameter parameter, Msg.Messages messages) {
super(ctx, method, annoMap, parameter, messages);
}
@Override
public String toString() {
return "@JdbcBatchUpdate";
}
}
// ------------------------------------------------------------------------
public static final class Native extends SourceMethod {
private final JavaAnnoType.JdbcNativeRecord annotation;
Native(Context ctx, ExecutableElement method, AnnoMap annoMap, SourceMethodParameter parameter,
JavaAnnoType.JdbcNativeRecord annotation, Msg.Messages messages) {
super(ctx, method, annoMap, parameter, messages);
this.annotation = annotation;
}
public JavaAnnoType.JdbcNativeRecord annotation() {
return this.annotation;
}
@Override
public String toString() {
return "@JdbcNative";
}
}
// ------------------------------------------------------------------------
public static final class CallMethod extends SourceMethod {
private final @Nullable SQLNameDV returnJdbcName; // FIXME hier oder doch im generate holen
private final @Nullable ConverterNameDV returnConverterName; // FIXME hier oder doch im generate holen
CallMethod(Context ctx, ExecutableElement method, AnnoMap annoMap, SourceMethodParameter parameter,
@Nullable SQLNameDV returnJdbcName, @Nullable ConverterNameDV returnConverterName,
Msg.Messages messages) {
super(ctx, method, annoMap, parameter, messages);
this.returnJdbcName = returnJdbcName;
this.returnConverterName = returnConverterName;
}
public @Nullable SQLNameDV returnJdbcName() {
return returnJdbcName;
}
public @Nullable ConverterNameDV returnConverterName() {
return returnConverterName;
}
@Override
public String toString() {
return "@JdbcCall";
}
}
// ------------------------------------------------------------------------
public static final class InvalidMethod extends SourceMethod {
InvalidMethod(Context ctx, ExecutableElement method, AnnoMap annoMap, SourceMethodParameter parameter, Msg.Messages messages) {
super(ctx, method, annoMap, parameter, messages);
}
static InvalidMethod of(Context ctx, ExecutableElement method, AnnoMap annoMap, Msg.Builder messages) {
var parameter = SourceMethodParameter.of(ctx, messages, method);
return new InvalidMethod(ctx, method, annoMap, parameter, messages.build());
}
@Override
public String toString() {
return "invalid";
}
}
}