All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.curs.celesta.plugin.maven.CursorGenerator Maven / Gradle / Ivy

The newest version!
package ru.curs.celesta.plugin.maven;

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;

import ru.curs.celesta.CallContext;
import ru.curs.celesta.CelestaException;
import ru.curs.celesta.ICelesta;

import ru.curs.celesta.dbutils.BasicCursor;
import ru.curs.celesta.dbutils.BasicDataAccessor;
import ru.curs.celesta.dbutils.CelestaGenerated;
import ru.curs.celesta.dbutils.Cursor;
import ru.curs.celesta.dbutils.CursorIterator;
import ru.curs.celesta.dbutils.MaterializedViewCursor;
import ru.curs.celesta.dbutils.ParameterizedViewCursor;
import ru.curs.celesta.dbutils.ReadOnlyTableCursor;
import ru.curs.celesta.dbutils.Sequence;
import ru.curs.celesta.dbutils.ViewCursor;
import ru.curs.celesta.event.TriggerType;
import ru.curs.celesta.score.BasicTable;
import ru.curs.celesta.score.BinaryColumn;
import ru.curs.celesta.score.Column;
import ru.curs.celesta.score.ColumnMeta;
import ru.curs.celesta.score.DataGrainElement;
import ru.curs.celesta.score.Grain;
import ru.curs.celesta.score.GrainElement;
import ru.curs.celesta.score.IntegerColumn;
import ru.curs.celesta.score.MaterializedView;
import ru.curs.celesta.score.NamedElement;
import ru.curs.celesta.score.Parameter;
import ru.curs.celesta.score.ParameterizedView;
import ru.curs.celesta.score.ReadOnlyTable;
import ru.curs.celesta.score.SequenceElement;
import ru.curs.celesta.score.StringColumn;
import ru.curs.celesta.score.Table;
import ru.curs.celesta.score.TableElement;
import ru.curs.celesta.score.VersionedElement;
import ru.curs.celesta.score.View;
import ru.curs.celesta.score.ZonedDateTimeColumn;
import ru.curs.celesta.score.io.FileResource;

import javax.annotation.Generated;
import javax.lang.model.element.Modifier;
import java.io.File;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public final class CursorGenerator {
    private static final String GRAIN_FIELD_NAME = "GRAIN_NAME";
    private static final String OBJECT_FIELD_NAME = "OBJECT_NAME";
    private static final String COLUMNS_FIELD_NAME = "COLUMNS";
    private static final HashMap<
            Class,
            Function>
            >
            GRAIN_ELEMENTS_TO_DATA_ACCESSORS = new HashMap<>();

    static {
        GRAIN_ELEMENTS_TO_DATA_ACCESSORS.put(SequenceElement.class, ge -> Sequence.class);
        GRAIN_ELEMENTS_TO_DATA_ACCESSORS.put(Table.class, ge -> Cursor.class);
        GRAIN_ELEMENTS_TO_DATA_ACCESSORS.put(ReadOnlyTable.class, ge -> ReadOnlyTableCursor.class);
        GRAIN_ELEMENTS_TO_DATA_ACCESSORS.put(View.class, ge -> ViewCursor.class);
        GRAIN_ELEMENTS_TO_DATA_ACCESSORS.put(MaterializedView.class, ge -> MaterializedViewCursor.class);
        GRAIN_ELEMENTS_TO_DATA_ACCESSORS.put(ParameterizedView.class, ge -> ParameterizedViewCursor.class);
    }

    private static final Map TRIGGER_REGISTRATION_METHOD_TO_TRIGGER_TYPE;

    static {
        Map map = new LinkedHashMap<>();
        map.put("onPreDelete", "PRE_DELETE");
        map.put("onPostDelete", "POST_DELETE");
        map.put("onPreInsert", "PRE_INSERT");
        map.put("onPostInsert", "POST_INSERT");
        map.put("onPreUpdate", "PRE_UPDATE");
        map.put("onPostUpdate", "POST_UPDATE");

        TRIGGER_REGISTRATION_METHOD_TO_TRIGGER_TYPE = Collections.unmodifiableMap(map);
    }

    private final File srcDir;
    private final boolean snakeToCamel;

    /**
     * Creates code generator for data accessor classes.
     *
     * @param srcDir       Path to directory where generated files should be put
     * @param snakeToCamel True if snake_case identifiers should be converted to camelCase
     */
    public CursorGenerator(File srcDir, boolean snakeToCamel) {
        this.srcDir = srcDir;
        this.snakeToCamel = snakeToCamel;
    }

    /**
     * Generate code for schema (grain) element.
     *
     * @param ge        Schema (grain) element
     * @param scorePath path to CelestaSQL file
     */
    public void generateCursor(GrainElement ge, String scorePath) {
        final String sourcePackage = calcSourcePackage(ge, scorePath);

        if (sourcePackage.isEmpty()) {
            throw new CelestaException(
                    "Couldn't generate class file for %s.%s without package",
                    ge.getGrain().getName(), ge.getName()
            );
        }

        final String className = calcClassName(ge);
        final String columnsClassName = "Columns";

        boolean isVersionedGe = ge instanceof VersionedElement && ((VersionedElement) ge).isVersioned();

        ClassName classType = ClassName.bestGuess(className);

        TypeSpec.Builder cursorClass = buildClassDefinition(ge, classType);

        ClassName columnsClassType = classType.nestedClass(columnsClassName);

        cursorClass.addFields(buildMetaFields(ge));

        cursorClass.addMethods(buildConstructors(ge));

        //FIELDS
        if (ge instanceof DataGrainElement) {
            DataGrainElement dge = (DataGrainElement) ge;

            FieldSpec columnsField = buildColumnsField(columnsClassType);
            cursorClass.addField(columnsField);
            cursorClass.addInitializerBlock(buildColumnsFiledInitializer(columnsField));

            List fieldSpecs = buildDataFields(dge);
            cursorClass.addFields(fieldSpecs);

            cursorClass.addMethods(generateGettersAndSetters(fieldSpecs, classType));

            cursorClass.addMethod(buildGetFieldValue(dge.getColumns()));
            cursorClass.addMethod(buildSetFieldValue(dge.getColumns()));

            StringBuilder parseResultOverridingMethodNameBuilder = new StringBuilder("_parseResult");

            Set> pk = Collections.emptySet();
            if (dge instanceof TableElement && !(dge instanceof ReadOnlyTable)) {
                TableElement te = (TableElement) dge;
                pk = new LinkedHashSet<>(te.getPrimaryKey().values());
                cursorClass.addMethod(buildCurrentKeyValues(pk));
                cursorClass.addMethod(buildTryGet(pk));
                cursorClass.addMethod(buildGet(pk));
                if (te instanceof Table) {
                    parseResultOverridingMethodNameBuilder.append("Internal");
                }
            }

            final Map> columns = dge.getColumns();

            MethodSpec buildParseResultMethod = buildParseResult(
                    columns, parseResultOverridingMethodNameBuilder.toString(), isVersionedGe);
            cursorClass.addMethod(buildParseResultMethod);

            cursorClass.addMethod(buildClearBuffer(columns, pk));

            cursorClass.addMethod(buildCurrentValues(columns));

            cursorClass.addType(buildCursorColumnsAsInnerStaticClass(dge, columnsClassType));

            if (dge instanceof BasicTable) {
                BasicTable t = (BasicTable) dge;
                if (t instanceof Table) {
                    cursorClass.addMethods(buildCalcBlobs(columns, className));
                    cursorClass.addMethod(buildSetAutoIncrement(columns));
                    cursorClass.addMethods(buildTriggerRegistration(classType));
                }
                cursorClass.addTypes(
                        buildOptionFieldsAsInnerStaticClasses(t.getColumns().values()));
            }

            cursorClass.addMethods(buildCompileCopying(ge, classType, columns.keySet(), isVersionedGe));
            cursorClass.addMethod(buildIterator(classType));
        }

        cursorClass.addMethods(buildGrainNameAndObjectName());

        JavaFile javaFile = JavaFile.builder(sourcePackage, cursorClass.build())
                .skipJavaLangImports(true)
                .indent("    ")
                .build();

        try {
            javaFile.writeTo(srcDir);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    static String calcSourcePackage(GrainElement ge, String scorePath) {
        String result;

        Grain g = ge.getGrain();
        if (g.getName().equals(g.getScore().getSysSchemaName())) {
            result = "ru.curs.celesta.syscursors";
        } else {
            String grainPartRelativePath =
                    new FileResource(new File(scorePath)).getRelativePath(ge.getGrainPart().getSource());
            result = Optional.of(grainPartRelativePath.lastIndexOf(File.separatorChar))
                    .filter(i -> i >= 0)
                    .map(i -> grainPartRelativePath.substring(0, i).replace(File.separator, "."))
                    .orElse("");
            if (result.startsWith(".")) {
                result = result.substring(1);
            }
        }

        return result;
    }

    private String calcClassName(GrainElement ge) {
        final String sourceFileNamePrefix = CaseUtils.capitalize(camelize(ge.getName()));
        if (ge instanceof SequenceElement) {
            return sourceFileNamePrefix + "Sequence";
        } else {
            return sourceFileNamePrefix + "Cursor";
        }
    }

    private String camelize(String s) {
        return snakeToCamel ? CaseUtils.snakeToCamel(s) : s;
    }

    private static String getCurrentDate() {
        return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
    }

    private static AnnotationSpec buildGeneratedAnnotation() {
        return AnnotationSpec.builder(Generated.class)
                .addMember("value", "$S", CursorGenerator.class.getCanonicalName())
                .addMember("date", "$S", getCurrentDate())
                .build();
    }

    private static TypeSpec.Builder buildClassDefinition(GrainElement ge, ClassName classType) {
        TypeSpec.Builder builder = TypeSpec.classBuilder(classType)
                .addModifiers(Modifier.PUBLIC)
                .superclass(GRAIN_ELEMENTS_TO_DATA_ACCESSORS.get(ge.getClass()).apply(ge))
                .addAnnotation(buildGeneratedAnnotation())
                .addAnnotation(AnnotationSpec.builder(CelestaGenerated.class).build());

        if (ge instanceof DataGrainElement) {
            builder.addSuperinterface(
                    ParameterizedTypeName.get(ClassName.get(Iterable.class), classType)
            );
        }
        if (ge instanceof BasicTable) {
            BasicTable t = (BasicTable) ge;
            if (!(t instanceof ReadOnlyTable)) {
                t.getImplements().forEach(
                        i -> builder.addSuperinterface(ClassName.bestGuess(i))
                );
            }
        }

        return builder;

    }

    private List buildOptionFieldsAsInnerStaticClasses(Collection> columns) {
        return columns.stream()
                .filter(c -> (c instanceof IntegerColumn || c instanceof StringColumn) && !c.getOptions().isEmpty())
                .map(
                        c -> {
                            TypeSpec.Builder builder = TypeSpec.classBuilder(
                                            CaseUtils.capitalize(camelize(c.getName())))
                                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                                    .addAnnotation(buildGeneratedAnnotation())
                                    .addAnnotation(CelestaGenerated.class);

                            MethodSpec constructor = MethodSpec.constructorBuilder()
                                    .addModifiers(Modifier.PRIVATE)
                                    .addStatement("throw new $T()", AssertionError.class)
                                    .build();

                            builder.addMethod(constructor);


                            List options = c.getOptions();
                            builder.addFields(
                                    options.stream().map(s -> {
                                                FieldSpec.Builder fb = FieldSpec.builder(
                                                        c.getJavaClass(), s,
                                                        Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL
                                                );

                                                if (c instanceof IntegerColumn) {
                                                    fb.initializer("$L", options.indexOf(s));
                                                } else {
                                                    fb.initializer("$S", s);
                                                }

                                                return fb.build();
                                            }
                                    ).collect(Collectors.toList())
                            );


                            return builder.build();
                        }
                ).collect(Collectors.toList());
    }

    private static List buildConstructors(GrainElement ge) {

        List results = new ArrayList<>();

        ParameterSpec contextParam = ParameterSpec.builder(CallContext.class, "context")
                .build();

        ParameterSpec columnsParam = ParameterSpec.builder(
                        ArrayTypeName.of(
                                ParameterizedTypeName.get(ClassName.get(ColumnMeta.class),
                                        WildcardTypeName.subtypeOf(Object.class))),
                        "columns")
                .build();

        ParameterSpec parametersParam = ParameterSpec.builder(
                        ParameterizedTypeName.get(Map.class, String.class, Object.class), "parameters")
                .build();

        Supplier msp = () -> MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(contextParam);

        // Common constructor
        MethodSpec.Builder builder = msp.get();
        if (ge instanceof ParameterizedView) {
            builder.addParameter(parametersParam);
            builder.addStatement("super(context, parameters)");
        } else {
            builder.addStatement("super(context)");
        }
        results.add(builder.build());

        if (ge instanceof SequenceElement) {
            return results;
        }

        // Constructor with columns limitation
        builder = msp.get();
        if (ge instanceof ParameterizedView) {
            builder.addParameter(parametersParam);
            builder.addParameter(columnsParam).varargs();
            builder.addStatement("super(context, parameters, columns)");
        } else {
            builder.addParameter(columnsParam).varargs();
            builder.addStatement("super(context, columns)");
        }
        results.add(builder.build());

        //ParameterizedView constructors
        if (ge instanceof ParameterizedView) {
            ParameterizedView pv = (ParameterizedView) ge;

            builder = msp.get();
            for (Parameter parameter : pv.getParameters().values()) {
                builder.addParameter(ParameterSpec.builder(
                        parameter.getJavaClass(), parameter.getName()
                ).build());
            }

            String spec = "super (context, paramsMap("
                    + pv.getParameters().values().stream().map(c -> "$N").collect(Collectors.joining(", "))
                    + "))";
            builder.addStatement(spec, pv.getParameters().keySet().toArray());
            results.add(builder.build());

            builder = msp.get();
            for (Parameter parameter : pv.getParameters().values()) {
                builder.addParameter(ParameterSpec.builder(
                        parameter.getJavaClass(), parameter.getName()
                ).build());
            }
            builder.addParameter(columnsParam).varargs();
            spec = "super (context, paramsMap("
                    + pv.getParameters().values().stream().map(c -> "$N").collect(Collectors.joining(", "))
                    + "), columns)";
            builder.addStatement(spec, pv.getParameters().keySet().toArray());
            results.add(builder.build());

            results.add(getParameterizedViewTypedConstructorHelper(pv));
        }
        return results;
    }

    private static MethodSpec getParameterizedViewTypedConstructorHelper(ParameterizedView pv) {
        MethodSpec.Builder builder = MethodSpec.methodBuilder("paramsMap")
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC).returns(
                        ParameterizedTypeName.get(Map.class,
                                String.class, Object.class));
        builder.addStatement("$T<$T,$T> params = new $T<>()",
                Map.class, String.class, Object.class, HashMap.class);
        for (Parameter parameter : pv.getParameters().values()) {
            String paramName = parameter.getName();
            builder.addParameter(ParameterSpec.builder(
                    parameter.getJavaClass(), paramName
            ).build());
            builder.addStatement("params.put($S, $N)", paramName, paramName);
        }
        builder.addStatement("return params");
        return builder.build();
    }

    private static List buildGrainNameAndObjectName() {
        MethodSpec grainName = MethodSpec.methodBuilder("_grainName")
                .addAnnotation(Override.class)
                .returns(String.class)
                .addModifiers(Modifier.PROTECTED)
                .addStatement("return $L", GRAIN_FIELD_NAME)
                .build();

        MethodSpec objectName = MethodSpec.methodBuilder("_objectName")
                .addAnnotation(Override.class)
                .returns(String.class)
                .addModifiers(Modifier.PROTECTED)
                .addStatement("return $L", OBJECT_FIELD_NAME)
                .build();

        return Arrays.asList(grainName, objectName);
    }

    private TypeSpec buildCursorColumnsAsInnerStaticClass(DataGrainElement dge, ClassName columnsClassType) {

        TypeSpec.Builder builder = TypeSpec.classBuilder(columnsClassType)
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                .addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
                        .addMember("value", "$S", "unchecked")
                        .build())
                .addAnnotation(buildGeneratedAnnotation())
                .addAnnotation(CelestaGenerated.class);

        FieldSpec elementField = FieldSpec.builder(
                        dge.getClass(), "element", Modifier.PRIVATE, Modifier.FINAL)
                .build();
        builder.addField(elementField);

        builder.addMethod(MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(ICelesta.class, "celesta")
                .addStatement("this.$N = celesta.getScore().getGrains().get($L).getElements($T.class).get($L)",
                        elementField, GRAIN_FIELD_NAME, elementField.type, OBJECT_FIELD_NAME)
                .build());

        dge.getColumns().entrySet().stream()
                .filter(e -> !BinaryColumn.CELESTA_TYPE.equals(e.getValue().getCelestaType()))
                .map(e -> {
                    final String columnName = e.getKey();
                    final TypeName columnType =
                            ParameterizedTypeName.get(ColumnMeta.class, e.getValue().getJavaClass());
                    return MethodSpec.methodBuilder(camelize(columnName))
                            .addModifiers(Modifier.PUBLIC)
                            .returns(columnType)
                            .addStatement("return ($T) this.$N.getColumns().get($S)",
                                    columnType, elementField, columnName)
                            .build();
                })
                .forEach(builder::addMethod);

        return builder.build();
    }

    private static List buildMetaFields(GrainElement ge) {

        final String grainName = ge.getGrain().getName();

        FieldSpec grainField = FieldSpec.builder(String.class, GRAIN_FIELD_NAME,
                        Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                .initializer("$S", grainName)
                .build();

        FieldSpec objectField = FieldSpec.builder(String.class, OBJECT_FIELD_NAME,
                        Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                .initializer("$S", ge.getName())
                .build();

        if (!grainName.equals(ge.getGrain().getScore().getSysSchemaName())) {
            return Arrays.asList(grainField, objectField);
        }

        FieldSpec tableField = FieldSpec.builder(String.class, "TABLE_NAME",
                        Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                .initializer("$N", objectField)
                .build();

        return Arrays.asList(grainField, objectField, tableField);
    }

    private static FieldSpec buildColumnsField(TypeName columnsClassType) {
        return FieldSpec.builder(columnsClassType, COLUMNS_FIELD_NAME, Modifier.PUBLIC, Modifier.FINAL)
                .build();
    }

    private static CodeBlock buildColumnsFiledInitializer(FieldSpec columnsField) {
        return CodeBlock.builder().addStatement(
                        "this.$N = new $T(callContext().getCelesta())", columnsField, columnsField.type)
                .build();
    }

    private List buildDataFields(DataGrainElement dge) {
        Map> columns = dge.getColumns();

        return columns.entrySet().stream()
                .map(e -> FieldSpec.builder(e.getValue().getJavaClass(), camelize(e.getKey()), Modifier.PRIVATE))
                .map(FieldSpec.Builder::build)
                .collect(Collectors.toList());

    }

    private List generateGettersAndSetters(List fieldSpecs, TypeName selfTypeName) {
        List result = new ArrayList<>();

        fieldSpecs.forEach(
                fieldSpec -> {
                    String methodSuffix = CaseUtils.capitalize(camelize(fieldSpec.name));

                    MethodSpec getter = MethodSpec.methodBuilder("get" + methodSuffix)
                            .addModifiers(Modifier.PUBLIC)
                            .returns(fieldSpec.type)
                            .addStatement("return this.$N", fieldSpec.name).build();
                    MethodSpec setter = MethodSpec.methodBuilder("set" + methodSuffix)
                            .addModifiers(Modifier.PUBLIC)
                            .returns(selfTypeName)
                            .addParameter(fieldSpec.type, fieldSpec.name)
                            .addStatement("this.$N = $N", fieldSpec.name, fieldSpec.name)
                            .addStatement("return this").build();

                    result.add(getter);
                    result.add(setter);
                }
        );

        return result;
    }

    private MethodSpec buildTryGet(Set> pk) {
        MethodSpec.Builder builder = MethodSpec.methodBuilder("tryGet")
                .addModifiers(Modifier.PUBLIC)
                .returns(TypeName.BOOLEAN);
        for (Column pkColumn : pk) {
            builder.addParameter(pkColumn.getJavaClass(), camelize(pkColumn.getName()));
        }
        String pkColumnNames = pk.stream().map(NamedElement::getName)
                .map(this::camelize)
                .collect(Collectors.joining(", "));
        builder.addStatement("return tryGetByValuesArray($N)", pkColumnNames);
        return builder.build();
    }

    private MethodSpec buildGet(Set> pk) {
        MethodSpec.Builder builder = MethodSpec.methodBuilder("get")
                .addModifiers(Modifier.PUBLIC);
        for (Column pkColumn : pk) {
            builder.addParameter(pkColumn.getJavaClass(), camelize(pkColumn.getName()));
        }
        String pkColumnNames = pk.stream().map(NamedElement::getName)
                .map(this::camelize).collect(Collectors.joining(", "));
        builder.addStatement("getByValuesArray($N)", pkColumnNames);
        return builder.build();
    }


    private MethodSpec buildParseResult(
            Map> columns, String methodName, boolean isVersionedObject
    ) {
        MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName)
                .addModifiers(Modifier.PROTECTED)
                .addAnnotation(Override.class)
                .addParameter(ResultSet.class, "rs")
                .addException(SQLException.class);

        columns.forEach((name, meta) -> {
            String cursorField = camelize(name);
            if (BinaryColumn.CELESTA_TYPE.equals(meta.getCelestaType())) {
                builder.addStatement("this.$N = null", cursorField);
            } else {
                builder.beginControlFlow("if (this.$N($S))", "inRec", name);
                if (ZonedDateTimeColumn.CELESTA_TYPE.equals(meta.getCelestaType())) {
                    builder.addStatement(
                            "$T ts = rs.$N($S, $T.getInstance($T.getTimeZone($S)))",
                            Timestamp.class, meta.jdbcGetterName(), name, Calendar.class, TimeZone.class, "UTC"
                    );
                    builder.beginControlFlow("if ($N != null)", "ts");
                    builder.addStatement("this.$N = $T.of(ts.toLocalDateTime(), $T.systemDefault())",
                            cursorField, ZonedDateTime.class, ZoneOffset.class);
                    builder.endControlFlow();
                    builder.beginControlFlow("else");
                    builder.addStatement("this.$N = null", cursorField);
                    builder.endControlFlow();
                } else {
                    builder.addStatement("this.$N = rs.$N($S)", cursorField, meta.jdbcGetterName(), name);
                    builder.beginControlFlow("if (rs.$N())", "wasNull");
                    builder.addStatement("this.$N = null", cursorField);
                    builder.endControlFlow();
                }
                builder.endControlFlow();
            }
        });

        if (isVersionedObject) {
            builder.addStatement("this.setRecversion(rs.getInt($S))", "recversion");
        }

        return builder.build();
    }

    private MethodSpec buildGetFieldValue(Map> columns) {
        String nameParam = "name";

        MethodSpec.Builder builder = MethodSpec.methodBuilder("_getFieldValue")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PROTECTED)
                .returns(TypeName.OBJECT)
                .addParameter(String.class, nameParam)
                .beginControlFlow("switch (name)");

        for (String columnName : columns.keySet()) {
            builder.addStatement("case $S: return this.$N", columnName, camelize(columnName));
        }
        builder.addStatement("default: return null").endControlFlow();

        return builder.build();
    }

    private MethodSpec buildSetFieldValue(Map> columns) {
        String nameParam = "name";
        String valueParam = "value";

        MethodSpec.Builder builder = MethodSpec.methodBuilder("_setFieldValue")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PROTECTED)
                .addParameter(String.class, nameParam)
                .addParameter(Object.class, valueParam)
                .beginControlFlow("switch (name)");
        for (Map.Entry> column : columns.entrySet()) {
            builder.beginControlFlow("case $S:", column.getKey())
                    .addStatement("this.$N = ($T) $N", camelize(column.getKey()),
                            column.getValue().getJavaClass(),
                            valueParam)
                    .addStatement("break")
                    .endControlFlow();
        }
        builder.addStatement("default:").endControlFlow();
        return builder.build();

    }

    private MethodSpec buildClearBuffer(Map> columns, Set> pk) {

        ParameterSpec param = ParameterSpec.builder(boolean.class, "withKeys").build();

        MethodSpec.Builder builder = MethodSpec.methodBuilder("_clearBuffer")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(param);

        if (!pk.isEmpty()) {
            builder.beginControlFlow("if ($N)", param.name);
            pk.forEach(c -> builder.addStatement("this.$N = null", camelize(c.getName())));
            builder.endControlFlow();
        }

        columns.entrySet().stream()
                .filter(e -> !pk.contains(e.getValue()))
                .forEach(e -> builder.addStatement("this.$N = null", camelize(e.getKey())));

        return builder.build();
    }

    private MethodSpec buildCurrentKeyValues(Set> pk) {

        ArrayTypeName resultType = ArrayTypeName.of(Object.class);

        MethodSpec.Builder builder = MethodSpec.methodBuilder("_currentKeyValues")
                .addModifiers(Modifier.PROTECTED)
                .addAnnotation(Override.class)
                .returns(resultType);
        String spec = "return new Object[] {"
                + pk.stream().map(c -> "$N").collect(Collectors.joining(", "))
                + "}";
        builder.addStatement(spec, pk.stream()
                .map(NamedElement::getName).map(this::camelize).toArray());
        return builder.build();
    }

    @SuppressWarnings("rawtypes")
    private MethodSpec buildCurrentValues(Map> columns) {
        ArrayTypeName resultType = ArrayTypeName.of(Object.class);

        MethodSpec.Builder builder = MethodSpec.methodBuilder("_currentValues")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .returns(resultType);
        String spec = "return new Object[] {"
                + columns.values().stream().map(c -> "$N").collect(Collectors.joining(", "))
                + "}";
        builder.addStatement(spec, columns.values().stream()
                .map(ColumnMeta::getName).map(this::camelize).toArray());

        return builder.build();
    }

    private List buildCalcBlobs(Map> columns, String className) {
        return columns.entrySet().stream()
                .filter(e -> e.getValue() instanceof BinaryColumn)
                .map(e ->
                        MethodSpec.methodBuilder("calc" + CaseUtils.capitalize(camelize(e.getKey())))
                                .addModifiers(Modifier.PUBLIC)
                                .addStatement("this.$N = this.calcBlob($S)", camelize(e.getKey()), e.getKey())
                                .addStatement(
                                        "(($N)this.getXRec()).$N = this.$N.clone()",
                                        className, camelize(e.getKey()), camelize(e.getKey())
                                ).build()
                ).collect(Collectors.toList());
    }

    private MethodSpec buildSetAutoIncrement(Map> columns) {
        MethodSpec.Builder builder = MethodSpec
                .methodBuilder("_setAutoIncrement")
                .addModifiers(Modifier.PROTECTED)
                .addAnnotation(Override.class);

        ParameterSpec param = ParameterSpec.builder(int.class, "val").build();

        builder.addParameter(param);

        columns.entrySet().stream()
                .filter(e -> e.getValue() instanceof IntegerColumn)
                .filter(e -> ((IntegerColumn) e.getValue()).getSequence() != null)
                .findAny()
                .ifPresent(e -> builder.addStatement("this.$N = $N", camelize(e.getKey()), param.name));

        return builder.build();
    }

    private static List buildTriggerRegistration(TypeName selfTypeName) {

        ParameterSpec celestaParam = ParameterSpec.builder(
                        ICelesta.class, "celesta")
                .build();
        ParameterSpec consumerParam = ParameterSpec.builder(
                        ParameterizedTypeName.get(ClassName.get(Consumer.class),
                                WildcardTypeName.supertypeOf(selfTypeName)),
                        "cursorConsumer")
                .build();

        return TRIGGER_REGISTRATION_METHOD_TO_TRIGGER_TYPE.entrySet().stream()
                .map(e -> {
                            String methodName = e.getKey();
                            String triggerType = e.getValue();

                            return MethodSpec.methodBuilder(methodName)
                                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                                    .addParameter(celestaParam)
                                    .addParameter(consumerParam)
                                    .addStatement(
                                            "$N.getTriggerDispatcher().registerTrigger($T.$N, $T.class, $N)",
                                            celestaParam.name, TriggerType.class, triggerType, selfTypeName,
                                            consumerParam.name
                                    ).build();
                        }
                ).collect(Collectors.toList());
    }

    private List buildCompileCopying(
            GrainElement ge, TypeName selfTypeName, Collection columns, boolean isVersionedObject
    ) {
        final String copyFieldsFromMethodName = "copyFieldsFrom";

        final ParameterSpec context = ParameterSpec.builder(CallContext.class, "context").build();

        final ParameterizedTypeName columnMetaOfQ = //ColumnMeta
                ParameterizedTypeName.get(ClassName.get(ColumnMeta.class),
                        WildcardTypeName.subtypeOf(Object.class));
        final ParameterSpec fields = ParameterSpec.builder(
                ParameterizedTypeName.get(ClassName.get(Collection.class),
                        WildcardTypeName.subtypeOf(columnMetaOfQ)), "fields"
        ).build();

        MethodSpec.Builder getBufferCopyBuilder = MethodSpec.methodBuilder("_getBufferCopy")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameters(Arrays.asList(context, fields))
                .returns(selfTypeName)
                .addStatement("final $T result", selfTypeName)
                .beginControlFlow("if ($T.isNull($N))", Objects.class, fields.name);


        if (ge instanceof ParameterizedView) {
            getBufferCopyBuilder.addStatement("result = new $T($N, this.parameters)", selfTypeName, context.name);
        } else {
            getBufferCopyBuilder.addStatement("result = new $T($N)", selfTypeName, context.name);
        }

        getBufferCopyBuilder.endControlFlow()
                .beginControlFlow("else");

        if (ge instanceof ParameterizedView) {
            getBufferCopyBuilder.addStatement(
                    "result = new $T($N, this.parameters, $N.toArray(new $T[0]))",
                    selfTypeName, context.name, fields.name, columnMetaOfQ
            );
        } else {
            getBufferCopyBuilder.addStatement(
                    "result = new $T($N, $N.toArray(new $T[0]))",
                    selfTypeName, context.name, fields.name, columnMetaOfQ
            );
        }

        getBufferCopyBuilder.endControlFlow()
                .addStatement("result.$N(this)", copyFieldsFromMethodName)
                .addStatement("return result");

        MethodSpec getBufferCopy = getBufferCopyBuilder.build();

        MethodSpec.Builder copyFieldsFromBuilder = MethodSpec.methodBuilder(copyFieldsFromMethodName)
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(BasicCursor.class, "c");

        copyFieldsFromBuilder.addStatement("$T from = ($T)c", selfTypeName, selfTypeName);

        columns.forEach(c ->
                copyFieldsFromBuilder.addStatement("this.$N = from.$N", camelize(c), camelize(c))
        );

        if (isVersionedObject) {
            copyFieldsFromBuilder.addStatement("this.setRecversion(from.getRecversion())");
        }

        return Arrays.asList(getBufferCopy, copyFieldsFromBuilder.build());
    }

    private static MethodSpec buildIterator(TypeName selfTypeName) {
        TypeName iteratorTypeName = ParameterizedTypeName.get(ClassName.get(Iterator.class), selfTypeName);
        TypeName cursorIterator = ParameterizedTypeName.get(ClassName.get(CursorIterator.class), selfTypeName);

        return MethodSpec.methodBuilder("iterator")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .returns(iteratorTypeName)
                .addStatement("return new $T(this)", cursorIterator)
                .build();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy