package com.siimkinks.sqlitemagic.writer; import com.google.common.base.Strings; import com.siimkinks.sqlitemagic.WriterUtil; import com.siimkinks.sqlitemagic.element.ColumnElement; import com.siimkinks.sqlitemagic.element.TableElement; import com.siimkinks.sqlitemagic.util.Callback2; import com.siimkinks.sqlitemagic.util.FormatData; import com.siimkinks.sqlitemagic.util.ReturnCallback; import com.siimkinks.sqlitemagic.util.ReturnCallback2; import com.siimkinks.sqlitemagic.util.StringUtil; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import java.util.List; import java.util.Set; import androidx.annotation.NonNull; import static com.siimkinks.sqlitemagic.BaseProcessor.GENERATE_LOGGING; import static com.siimkinks.sqlitemagic.Const.PUBLIC_STATIC_FINAL; import static com.siimkinks.sqlitemagic.Const.STATEMENT_METHOD_MAP; import static com.siimkinks.sqlitemagic.Const.STATIC_METHOD_MODIFIERS; import static com.siimkinks.sqlitemagic.GlobalConst.ERROR_UNSUBSCRIBED_UNEXPECTEDLY; import static com.siimkinks.sqlitemagic.WriterUtil.BIND_VALUES_MAP; import static com.siimkinks.sqlitemagic.WriterUtil.COLUMN; import static com.siimkinks.sqlitemagic.WriterUtil.DISPOSABLE; import static com.siimkinks.sqlitemagic.WriterUtil.DISPOSABLES; import static com.siimkinks.sqlitemagic.WriterUtil.LOG_UTIL; import static com.siimkinks.sqlitemagic.WriterUtil.OPERATION_FAILED_EXCEPTION; import static com.siimkinks.sqlitemagic.WriterUtil.SQLITE_MAGIC; import static com.siimkinks.sqlitemagic.WriterUtil.SQL_UTIL; import static com.siimkinks.sqlitemagic.WriterUtil.STRING; import static com.siimkinks.sqlitemagic.WriterUtil.SUPPORT_SQLITE_STATEMENT; import static com.siimkinks.sqlitemagic.WriterUtil.TRANSACTION; import static com.siimkinks.sqlitemagic.WriterUtil.addTableTriggersSendingStatement; import static com.siimkinks.sqlitemagic.WriterUtil.codeBlockEnd; import static com.siimkinks.sqlitemagic.WriterUtil.emitterOnComplete; import static com.siimkinks.sqlitemagic.WriterUtil.emitterOnError; import static com.siimkinks.sqlitemagic.WriterUtil.ifDisposed; import static com.siimkinks.sqlitemagic.util.NameConst.FIELD_INSERT_SQL; import static com.siimkinks.sqlitemagic.util.NameConst.FIELD_TABLE_SCHEMA; import static com.siimkinks.sqlitemagic.util.NameConst.FIELD_UPDATE_SQL; import static com.siimkinks.sqlitemagic.util.NameConst.METHOD_BIND_TO_NOT_NULL; import static com.siimkinks.sqlitemagic.util.NameConst.METHOD_BIND_UNIQUE_COLUMN; import static com.siimkinks.sqlitemagic.util.NameConst.METHOD_IS_UNIQUE_COLUMN_NULL; import static com.siimkinks.sqlitemagic.util.NameConst.METHOD_SET_ID; import static com.siimkinks.sqlitemagic.writer.ModelWriter.DB_CONNECTION_VARIABLE; import static com.siimkinks.sqlitemagic.writer.ModelWriter.DISPOSABLE_VARIABLE; import static com.siimkinks.sqlitemagic.writer.ModelWriter.EMITTER_VARIABLE; import static com.siimkinks.sqlitemagic.writer.ModelWriter.ENTITY_VARIABLE; import static com.siimkinks.sqlitemagic.writer.ModelWriter.OPERATION_BY_COLUMNS_VARIABLE; import static com.siimkinks.sqlitemagic.writer.ModelWriter.OPERATION_HELPER_VARIABLE; import static com.siimkinks.sqlitemagic.writer.ModelWriter.TRANSACTION_VARIABLE; import static com.siimkinks.sqlitemagic.writer.ModelWriter.UPDATE_BY_COLUMN_VARIABLE; // FIXME !!! check logging generation public class ModelPersistingGenerator implements ModelPartGenerator { @Override public void write(TypeSpec.Builder daoClassBuilder, TypeSpec.Builder handlerClassBuilder, EntityEnvironment entityEnvironment) { final TableElement tableElement = entityEnvironment.getTableElement(); final Set<TableElement> allTableTriggers = tableElement.getAllTableTriggers(); final InsertWriter insertWriter = InsertWriter.create(entityEnvironment, allTableTriggers); final OperationWriter[] writers = new OperationWriter[]{ insertWriter, UpdateWriter.create(entityEnvironment, allTableTriggers), PersistWriter.create(entityEnvironment, allTableTriggers, insertWriter) }; writeDao(daoClassBuilder, entityEnvironment); writeHandler(handlerClassBuilder, entityEnvironment); for (OperationWriter writer : writers) { writer.writeDao(daoClassBuilder); writer.writeHandler(handlerClassBuilder); } } public void writeDao(TypeSpec.Builder daoClassBuilder, EntityEnvironment entityEnvironment) { final TableElement tableElement = entityEnvironment.getTableElement(); if (tableElement.hasIdSetter()) { daoClassBuilder.addMethod(entityEnvironment.getEntityIdSetter()); } daoClassBuilder.addMethod(bindToNotNullContentValues(entityEnvironment)); if (tableElement.hasUniqueColumnsOtherThanId()) { final TypeName tableElementTypeName = entityEnvironment.getTableElementTypeName(); daoClassBuilder.addMethod(bindUniqueColumn(tableElement, tableElementTypeName)); if (tableElement.isAnyUniqueColumnNullable()) { daoClassBuilder.addMethod(isUniqueColumnNull(tableElement, tableElementTypeName)); } } } public void writeHandler(TypeSpec.Builder handlerClassBuilder, EntityEnvironment entityEnvironment) { final TableElement tableElement = entityEnvironment.getTableElement(); handlerClassBuilder.addField(schema(tableElement)) .addField(insertSqlField(tableElement)) .addField(updateSqlField(tableElement)); } // ------------------------------------------- // DAO methods // ------------------------------------------- private MethodSpec isUniqueColumnNull(TableElement tableElement, TypeName tableElementTypeName) { final MethodSpec.Builder builder = MethodSpec.methodBuilder(METHOD_IS_UNIQUE_COLUMN_NULL) .addModifiers(STATIC_METHOD_MODIFIERS) .addParameter(STRING, "columnName") .addParameter(tableElementTypeName, ENTITY_VARIABLE) .returns(TypeName.BOOLEAN) .beginControlFlow("switch (columnName)"); for (ColumnElement column : tableElement.getAllColumns()) { if (column.isUnique() || column.isId()) { builder.addCode("case $S:\n", column.getColumnName()); final boolean columnNullable = column.isNullable(); if (columnNullable) { if (column.isReferencedColumn() && column.getReferencedTable().getIdColumn().isNullable()) { final TableElement referencedTable = column.getReferencedTable(); final String valueGetter = column.valueGetter(ENTITY_VARIABLE); final String variableName = column.getElementName(); builder.addStatement("final $T $L = $L", referencedTable.getTableElementTypeName(), variableName, valueGetter); final FormatData idGetterFromDao = EntityEnvironment.idGetterFromDaoIfNeeded(column, variableName); builder.beginControlFlow(idGetterFromDao.formatInto("return $L == null || %s == null"), idGetterFromDao.getWithOtherArgsBefore(variableName)); } else { final String valueGetter = column.valueGetter(ENTITY_VARIABLE); builder.addStatement("return $L == null", valueGetter); } } else { builder.addStatement("return false"); } } } builder.endControlFlow(); builder.addStatement("throw new $T(\"Column \" + columnName + \" is not unique\")", IllegalStateException.class); return builder.build(); } private MethodSpec bindUniqueColumn(TableElement tableElement, TypeName tableElementTypeName) { final MethodSpec.Builder builder = MethodSpec.methodBuilder(METHOD_BIND_UNIQUE_COLUMN) .addModifiers(STATIC_METHOD_MODIFIERS) .addParameter(SUPPORT_SQLITE_STATEMENT, "statement") .addParameter(TypeName.INT, "index") .addParameter(STRING, "columnName") .addParameter(tableElementTypeName, ENTITY_VARIABLE) .beginControlFlow("switch (columnName)"); for (ColumnElement column : tableElement.getAllColumns()) { if (column.isUnique() || column.isId()) { builder .addCode("case $S:\n", column.getColumnName()) .addCode(createBindColumnToStatement("index", column)) .addStatement("break"); } } builder.endControlFlow(); return builder.build(); } private MethodSpec bindToNotNullContentValues(EntityEnvironment entityEnvironment) { final CodeBlock.Builder valuesGatherBlock = buildNotNullValuesGatheringBlock(entityEnvironment.getTableElement()); MethodSpec.Builder builder = bindToMap(entityEnvironment, METHOD_BIND_TO_NOT_NULL, valuesGatherBlock); addImmutableIdsParameterIfNeeded(builder, entityEnvironment.getTableElement()); return builder.build(); } private MethodSpec.Builder bindToMap(EntityEnvironment entityEnvironment, String methodName, CodeBlock.Builder valuesGatherBlock) { final MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName) .addModifiers(STATIC_METHOD_MODIFIERS) .addParameter(entityEnvironment.getTableElementTypeName(), ENTITY_VARIABLE) .addParameter(BIND_VALUES_MAP, "values"); builder.addStatement("values.clear()") .addCode(valuesGatherBlock.build()); return builder; } private CodeBlock.Builder buildNotNullValuesGatheringBlock(TableElement tableElement) { CodeBlock.Builder valuesGatherBlock = CodeBlock.builder(); int immutableIdColPos = 0; for (final ColumnElement columnElement : tableElement.getAllColumns()) { if (columnElement.isHandledRecursively() && columnElement.isReferencedTableImmutable()) { addBindFromProvidedIdsToContentValues(valuesGatherBlock, immutableIdColPos, columnElement); immutableIdColPos++; } else { valuesGatherBlock.add(createBindBlockWithChecks(columnElement, new Callback2<CodeBlock.Builder, FormatData>() { @Override public void call(CodeBlock.Builder builder, FormatData serializedValueGetter) { builder.addStatement(String.format("values.put($S, %s)", serializedValueGetter.getFormat()), serializedValueGetter.getWithOtherArgsBefore(columnElement.getColumnName())); } }).build()); } } return valuesGatherBlock; } private void addBindFromProvidedIdsToContentValues(CodeBlock.Builder valuesGatherBlock, int immutableIdColPos, ColumnElement columnElement) { if (columnElement.isNullable()) { valuesGatherBlock.beginControlFlow("if (ids[$L] > 0)", immutableIdColPos); } valuesGatherBlock.addStatement("values.put($S, ids[$L])", columnElement.getColumnName(), immutableIdColPos); if (columnElement.isNullable()) { valuesGatherBlock.endControlFlow(); } } static void addBindColumnFromProvidedIdsBlock(MethodSpec.Builder builder, ColumnElement columnElement, int colPos, int immutableIdColPos) { final String bindMethod = STATEMENT_METHOD_MAP.get(columnElement.getSerializedType().getQualifiedName()); if (columnElement.isNullable()) { builder.beginControlFlow("if (ids[$L] > 0)", immutableIdColPos); } builder.addStatement("statement.$L($L, ids[$L])", bindMethod, colPos, immutableIdColPos); if (columnElement.isNullable()) { builder.endControlFlow(); } } static void addBindColumnToStatementBlock(MethodSpec.Builder builder, final int colPos, final ColumnElement columnElement) { builder.addCode(createBindBlockWithChecks(columnElement, new Callback2<CodeBlock.Builder, FormatData>() { @Override public void call(CodeBlock.Builder builder, FormatData serializedValueGetter) { final String bindMethod = STATEMENT_METHOD_MAP.get(columnElement.getSerializedType().getQualifiedName()); builder.addStatement(String.format("statement.$L($L, %s)", serializedValueGetter.getFormat()), serializedValueGetter.getWithOtherArgsBefore(bindMethod, colPos)); } }).build()); } static CodeBlock.Builder createBindBlockWithChecks(ColumnElement columnElement, Callback2<CodeBlock.Builder, FormatData> realBindAddingCallback) { CodeBlock.Builder builder = CodeBlock.builder(); final boolean columnNullable = columnElement.isNullable(); if (columnElement.isReferencedColumn()) { final TableElement referencedTable = columnElement.getReferencedTable(); final ColumnElement referencedTableIdColumn = referencedTable.getIdColumn(); final boolean referencedIdColumnNullable = referencedTableIdColumn.isNullable(); if (!columnNullable && !referencedIdColumnNullable) { realBindAddingCallback.call(builder, columnElement.serializedValueGetterFromEntity(ENTITY_VARIABLE)); } else { final String valueGetter = columnElement.valueGetter(ENTITY_VARIABLE); final String variableName = columnElement.getElementName(); builder.addStatement("final $T $L = $L", referencedTable.getTableElementTypeName(), variableName, valueGetter); final FormatData idGetterFromDao = EntityEnvironment.idGetterFromDaoIfNeeded(columnElement, variableName); if (columnNullable && referencedIdColumnNullable) { builder.beginControlFlow(idGetterFromDao.formatInto("if ($L != null && %s != null)"), idGetterFromDao.getWithOtherArgsBefore(variableName)); } else if (columnNullable) { builder.beginControlFlow("if ($L != null)", variableName); } else { builder.beginControlFlow(idGetterFromDao.formatInto("if (%s != null)"), idGetterFromDao.getArgs()); } realBindAddingCallback.call(builder, idGetterFromDao); builder.endControlFlow(); } } else { final FormatData serializedValueGetter = columnElement.serializedValueGetterFromEntity(ENTITY_VARIABLE); if (!columnNullable) { realBindAddingCallback.call(builder, serializedValueGetter); } else { final String valueGetter = columnElement.valueGetter(ENTITY_VARIABLE); builder.beginControlFlow("if ($L != null)", valueGetter); realBindAddingCallback.call(builder, serializedValueGetter); builder.endControlFlow(); } } return builder; } static CodeBlock createBindColumnToStatement(final String columnIndexVariable, final ColumnElement columnElement) { final Callback2<CodeBlock.Builder, FormatData> bindFunction = new Callback2<CodeBlock.Builder, FormatData>() { @Override public void call(CodeBlock.Builder builder, FormatData serializedValueGetter) { final String bindMethod = STATEMENT_METHOD_MAP.get(columnElement.getSerializedType().getQualifiedName()); builder.addStatement(String.format("statement.$L($L, %s)", serializedValueGetter.getFormat()), serializedValueGetter.getWithOtherArgsBefore(bindMethod, columnIndexVariable)); } }; final CodeBlock.Builder builder = CodeBlock.builder(); final boolean columnNullable = columnElement.isNullable(); if (columnElement.isReferencedColumn()) { final TableElement referencedTable = columnElement.getReferencedTable(); final ColumnElement referencedTableIdColumn = referencedTable.getIdColumn(); final boolean referencedIdColumnNullable = referencedTableIdColumn.isNullable(); if (!columnNullable && !referencedIdColumnNullable) { bindFunction.call(builder, columnElement.serializedValueGetterFromEntity(ENTITY_VARIABLE)); } else { final String valueGetter = columnElement.valueGetter(ENTITY_VARIABLE); final String variableName = columnElement.getElementName(); builder.addStatement("final $T $L = $L", referencedTable.getTableElementTypeName(), variableName, valueGetter); final FormatData idGetterFromDao = EntityEnvironment.idGetterFromDaoIfNeeded(columnElement, variableName); if (columnNullable && referencedIdColumnNullable) { builder.beginControlFlow(idGetterFromDao.formatInto("if ($L == null || %s == null)"), idGetterFromDao.getWithOtherArgsBefore(variableName)); } else if (columnNullable) { builder.beginControlFlow("if ($L == null)", variableName); } else { builder.beginControlFlow(idGetterFromDao.formatInto("if (%s == null)"), idGetterFromDao.getArgs()); } builder.addStatement("throw new $T(\"$L column is null\")", NullPointerException.class, columnElement.getColumnName()); builder.endControlFlow(); bindFunction.call(builder, idGetterFromDao); } } else { final FormatData serializedValueGetter = columnElement.serializedValueGetterFromEntity(ENTITY_VARIABLE); if (!columnNullable) { bindFunction.call(builder, serializedValueGetter); } else { final String valueGetter = columnElement.valueGetter(ENTITY_VARIABLE); builder.beginControlFlow("if ($L == null)", valueGetter) .addStatement("throw new $T(\"$L column is null\")", NullPointerException.class, columnElement.getColumnName()) .endControlFlow(); bindFunction.call(builder, serializedValueGetter); } } return builder.build(); } static CodeBlock updateByColumnVariable(TableElement tableElement) { return CodeBlock.builder() .addStatement("final $T column = $T.firstColumnForTable($S, $L)", COLUMN, SQL_UTIL, tableElement.getTableName(), OPERATION_BY_COLUMNS_VARIABLE) .addStatement("final $T $L", STRING, UPDATE_BY_COLUMN_VARIABLE) .beginControlFlow("if (column != null)") .addStatement("$L = column.name", UPDATE_BY_COLUMN_VARIABLE) .nextControlFlow("else") .addStatement("$L = $S", UPDATE_BY_COLUMN_VARIABLE, tableElement.getIdColumn().getColumnName()) .endControlFlow() .build(); } static final ReturnCallback2<String, ParameterSpec, ColumnElement> COMPLEX_COLUMN_PARAM_TO_ENTITY_DB_MANAGER = new ReturnCallback2<String, ParameterSpec, ColumnElement>() { @Override public String call(ParameterSpec param, ColumnElement columnElement) { if (DB_CONNECTION_VARIABLE.equals(param.name)) { final TableElement referencedTable = columnElement.getReferencedTable(); return param.name + ".getEntityDbManager(" + referencedTable.getEnvironment().getSubmoduleName() + ", " + referencedTable.getTablePos() + ")"; } return param.name; } }; static void addMethodInternalCallOnComplexColumnsIfNeeded(TypeSpec.Builder daoClassBuilder, EntityEnvironment entityEnvironment, String methodName, ReturnCallback<String, ColumnElement> callableMethodCallback, ParameterSpec... params) { addMethodInternalCallOnComplexColumnsIfNeeded(daoClassBuilder, entityEnvironment, methodName, new ReturnCallback2<String, ParameterSpec, ColumnElement>() { @Override public String call(ParameterSpec param, ColumnElement columnElement) { return param.name; } }, callableMethodCallback, params); } static void addMethodInternalCallOnComplexColumnsIfNeeded(TypeSpec.Builder daoClassBuilder, EntityEnvironment entityEnvironment, String methodName, ReturnCallback2<String, ParameterSpec, ColumnElement> paramEval, ReturnCallback<String, ColumnElement> callableMethodCallback, ParameterSpec... params) { final TableElement tableElement = entityEnvironment.getTableElement(); if (tableElement.hasAnyPersistedComplexColumns()) { MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName) .addModifiers(STATIC_METHOD_MODIFIERS) .addParameter(entityEnvironment.getTableElementTypeName(), ENTITY_VARIABLE); for (ParameterSpec param : params) { methodBuilder.addParameter(param); } boolean hasAnyPersistedImmutableComplexColumns = tableElement.hasAnyPersistedImmutableComplexColumns(); if (hasAnyPersistedImmutableComplexColumns) { methodBuilder.returns(long[].class) .addStatement("final long[] ids = new long[$L]", tableElement.getPersistedImmutableComplexColumnCount()); } int pos = 0; for (ColumnElement columnElement : tableElement.getAllColumns()) { if (columnElement.isHandledRecursively()) { final String callableMethodName = callableMethodCallback.call(columnElement); final String valueGetter = columnElement.valueGetter(ENTITY_VARIABLE); final ClassName referencedModelHandler = EntityEnvironment.getGeneratedHandlerClassName(columnElement.getReferencedTable()); if (columnElement.isNullable()) { methodBuilder.beginControlFlow("if ($L != null)", valueGetter); } final StringBuilder sb = new StringBuilder(); for (ParameterSpec param : params) { sb.append(", ") .append(paramEval.call(param, columnElement)); } if (columnElement.isReferencedTableImmutable()) { methodBuilder.addStatement("ids[$L] = $T.$L($L$L)", pos, referencedModelHandler, callableMethodName, valueGetter, sb.toString()); } else { methodBuilder.addStatement("$T.$L($L$L)", referencedModelHandler, callableMethodName, valueGetter, sb.toString()); } if (columnElement.isNullable()) { methodBuilder.endControlFlow(); } if (columnElement.isReferencedTableImmutable()) { pos++; } } } if (hasAnyPersistedImmutableComplexColumns) { methodBuilder.addStatement("return ids"); } daoClassBuilder.addMethod(methodBuilder.build()); } } // ------------------------------------------- // Handler methods // ------------------------------------------- private FieldSpec schema(TableElement tableElement) { return FieldSpec.builder(String.class, FIELD_TABLE_SCHEMA) .addModifiers(PUBLIC_STATIC_FINAL) .initializer("$S", tableElement.getSchema()) .build(); } static void addIdNullCheck(MethodSpec.Builder builder, String errMsg) { builder.beginControlFlow("if (id == null)") .addStatement("throw new NullPointerException($S)", errMsg) .endControlFlow(); } static CodeBlock statementWithImmutableIdsIfNeeded(TableElement tableElement, String statement, Object... args) { return CodeBlock.builder() .add(statement, args) .add(tableElement.hasAnyPersistedImmutableComplexColumns() ? ", ids)" : ")") .add(WriterUtil.codeBlockEnd()) .build(); } static void addImmutableIdsParameterIfNeeded(MethodSpec.Builder builder, TableElement tableElement) { if (tableElement.hasAnyPersistedImmutableComplexColumns()) { builder.addParameter(long[].class, "ids"); } } static void addCheckIdValidity(MethodSpec.Builder builder, String errMsg) { builder.beginControlFlow("if (id == -1)"); addThrowOperationFailedExceptionWithEntityVariable(builder, errMsg); builder.endControlFlow(); } static void addIdValidityRespectingConflictAbort(MethodSpec.Builder builder, TableElement tableElement, ClassName generatedModelDaoClassName, String errMsg) { builder.beginControlFlow("if (id == -1)") .beginControlFlow("if (!$L.ignoreConflict)", OPERATION_HELPER_VARIABLE); addThrowOperationFailedExceptionWithEntityVariable(builder, errMsg); builder.endControlFlow() .nextControlFlow("else"); if (isIdSettingNeeded(tableElement)) { builder.addStatement("$T.$L($L, id)", generatedModelDaoClassName, METHOD_SET_ID, ENTITY_VARIABLE); } builder.addStatement("atLeastOneSuccess = true") .endControlFlow(); } static void addInlineExecuteInsertWithCheckIdValidity(MethodSpec.Builder builder, String insertStmVariableName, String errMsg) { builder.beginControlFlow("if ($L.executeInsert() == -1)", insertStmVariableName); addThrowOperationFailedExceptionWithEntityVariable(builder, errMsg); builder.endControlFlow(); } static void addSetIdStatementIfNeeded(TableElement tableElement, ClassName generatedModelDaoClassName, MethodSpec.Builder builder) { if (isIdSettingNeeded(tableElement)) { builder.addStatement("$T.$L($L, id)", generatedModelDaoClassName, METHOD_SET_ID, ENTITY_VARIABLE); } } static boolean isIdSettingNeeded(TableElement tableElement) { return !tableElement.isImmutable() && tableElement.getIdColumn().isAutoincrementId(); } static void addTopMethodStartBlock(MethodSpec.Builder builder, boolean hasComplexColumns) { if (hasComplexColumns) { addTransactionStartBlock(builder); } else { builder.beginControlFlow("try"); } } static void addThrowOperationFailedExceptionWithEntityVariable(MethodSpec.Builder builder, String errMsg) { builder.addStatement("throw new $T($S + $L)", OPERATION_FAILED_EXCEPTION, errMsg, ENTITY_VARIABLE); } static void addOperationFailedWhenDisposed(MethodSpec.Builder builder) { builder.beginControlFlow(ifDisposed()) .addStatement("throw new $T($S)", OPERATION_FAILED_EXCEPTION, ERROR_UNSUBSCRIBED_UNEXPECTEDLY) .endControlFlow(); } static void addTopMethodEndBlock(@NonNull MethodSpec.Builder builder, @NonNull Set<TableElement> allTableTriggers, boolean hasComplexColumns, @NonNull String returnStatement, @NonNull String failReturnStatement, boolean closeOpHelper) { addTopMethodEndBlock(builder, allTableTriggers, hasComplexColumns, CodeBlock.builder().addStatement(returnStatement).build(), failReturnStatement, closeOpHelper); } static void addTopMethodEndBlock(MethodSpec.Builder builder, Set<TableElement> allTableTriggers, boolean hasComplexColumns, CodeBlock returnStatement, String failReturnStatement, boolean closeOpHelper) { if (hasComplexColumns) { addTransactionEndBlock(builder, allTableTriggers, returnStatement, failReturnStatement, closeOpHelper); } else { addTableTriggersSendingStatement(builder, allTableTriggers); builder.addCode(returnStatement) .nextControlFlow("catch ($T e)", OPERATION_FAILED_EXCEPTION); addOperationFailedLoggingStatement(builder); builder.addStatement(failReturnStatement); if (closeOpHelper) { builder.nextControlFlow("finally") .addStatement("$L.close()", OPERATION_HELPER_VARIABLE); } builder.endControlFlow(); } } public static void addTransactionStartBlock(MethodSpec.Builder builder) { builder.addStatement("final $T $L = $L.newTransaction()", TRANSACTION, TRANSACTION_VARIABLE, DB_CONNECTION_VARIABLE) .addStatement("boolean success = false") .beginControlFlow("try"); } public static void addDisposableForEmitter(MethodSpec.Builder builder) { builder .addStatement("final $T $L = $T.empty()", DISPOSABLE, DISPOSABLE_VARIABLE, DISPOSABLES) .addStatement("$L.setDisposable($L)", EMITTER_VARIABLE, DISPOSABLE_VARIABLE); } static void addTransactionEndBlock(@NonNull MethodSpec.Builder builder, @NonNull Set<TableElement> allTableTriggers, @NonNull String returnStatement, @NonNull String failReturnStatement, boolean closeOpHelper) { addTransactionEndBlock(builder, allTableTriggers, CodeBlock.builder().addStatement(returnStatement).build(), failReturnStatement, closeOpHelper); } static void addTransactionEndBlock(@NonNull MethodSpec.Builder builder, @NonNull Set<TableElement> allTableTriggers, @NonNull CodeBlock returnStatement, @NonNull String failReturnStatement, boolean closeOpHelper) { addTransactionEndBlock(builder, allTableTriggers, CodeBlock.builder() .addStatement("success = true") .build(), returnStatement, failReturnStatement, closeOpHelper); } static void addTransactionEndBlock(@NonNull MethodSpec.Builder builder, @NonNull Set<TableElement> allTableTriggers, @NonNull CodeBlock successStatement, @NonNull CodeBlock returnStatement, @NonNull String failReturnStatement, boolean closeOpHelper) { builder.addStatement("$L.markSuccessful()", TRANSACTION_VARIABLE) .addCode(successStatement) .addCode(returnStatement) .nextControlFlow("catch ($T e)", OPERATION_FAILED_EXCEPTION); addOperationFailedLoggingStatement(builder); if (!Strings.isNullOrEmpty(failReturnStatement)) { builder.addStatement(failReturnStatement); } builder.nextControlFlow("finally") .addStatement("$L.end()", TRANSACTION_VARIABLE) .beginControlFlow("if (success)"); addTableTriggersSendingStatement(builder, allTableTriggers); builder.endControlFlow(); if (closeOpHelper) { builder.addStatement("$L.close()", OPERATION_HELPER_VARIABLE); } builder.endControlFlow(); } public static void addRxCompletableEmitterTransactionEndBlock(@NonNull MethodSpec.Builder builder, @NonNull Set<TableElement> allTableTriggers) { addRxCompletableEmitterTransactionEndBlock(builder, allTableTriggers, CodeBlock.builder() .addStatement("success = true") .build()); } public static void addRxCompletableEmitterTransactionEndBlock(@NonNull MethodSpec.Builder builder, @NonNull Set<TableElement> allTableTriggers, @NonNull CodeBlock successStatement) { builder .addStatement("$L.markSuccessful()", TRANSACTION_VARIABLE) .addCode(successStatement) .nextControlFlow("catch ($T e)", Throwable.class) .addCode(emitterOnError()) .nextControlFlow("finally") .addStatement("$L.end()", TRANSACTION_VARIABLE) .beginControlFlow("if (success)"); addTableTriggersSendingStatement(builder, allTableTriggers); builder.endControlFlow() .addStatement(emitterOnComplete()) .endControlFlow(); } static void addCallToComplexColumnsOperationWithVariableValuesIfNeeded(MethodSpec.Builder builder, EntityEnvironment entityEnvironment, String complexColumnsOperationMethodName, String... params) { final TableElement tableElement = entityEnvironment.getTableElement(); if (tableElement.hasAnyPersistedComplexColumns()) { String extraParamsToInternalMethodCall = ""; for (String param : params) { extraParamsToInternalMethodCall += ", " + param; } final CodeBlock.Builder statementBuilder = CodeBlock.builder(); if (tableElement.hasAnyPersistedImmutableComplexColumns()) { statementBuilder.add("final long[] ids = "); } statementBuilder.add("$T.$L($L$L)", entityEnvironment.getDaoClassName(), complexColumnsOperationMethodName, ENTITY_VARIABLE, extraParamsToInternalMethodCall); statementBuilder.add(codeBlockEnd()); builder.addCode(statementBuilder.build()); } } private FieldSpec insertSqlField(TableElement tableElement) { final List<? extends ColumnElement> allColumns = tableElement.getAllColumns(); final StringBuilder insertSql = new StringBuilder(); insertSql.append("INSERT%s INTO "); insertSql.append(tableElement.getTableName()); insertSql.append(" ("); boolean firstTime = true; int columnCount = 0; for (ColumnElement column : allColumns) { if (column.isId() && column.isAutoincrementId()) { continue; } if (firstTime) { firstTime = false; } else { insertSql.append(", "); } insertSql.append(column.getColumnName()); columnCount++; } insertSql.append(") VALUES ("); StringUtil.append(", ", "?", columnCount, insertSql); insertSql.append(")"); return FieldSpec.builder(String.class, FIELD_INSERT_SQL) .addModifiers(PUBLIC_STATIC_FINAL) .initializer("$S", insertSql.toString()) .build(); } private FieldSpec updateSqlField(TableElement tableElement) { final List<ColumnElement> columnsExceptId = tableElement.getColumnsExceptId(); final StringBuilder updateSql = new StringBuilder(); updateSql.append("UPDATE%s ") .append(tableElement.getTableName()) .append(" SET "); StringUtil.join(", ", columnsExceptId, updateSql, new StringUtil.AppendCallback<ColumnElement>() { @Override public void append(@NonNull StringBuilder sb, @NonNull ColumnElement column) { sb.append(column.getColumnName()) .append("=?"); } }); updateSql.append(" WHERE ") .append(tableElement.getIdColumn().getColumnName()) .append("=?"); return FieldSpec.builder(String.class, FIELD_UPDATE_SQL) .addModifiers(PUBLIC_STATIC_FINAL) .initializer("$S", updateSql.toString()) .build(); } private static void addOperationFailedLoggingStatement(MethodSpec.Builder builder) { if (GENERATE_LOGGING) { builder.addStatement("if ($T.LOGGING_ENABLED) $T.logError(e, \"Operation failed\")", SQLITE_MAGIC, LOG_UTIL); } } }