package co.smartreceipts.android.persistence.database.tables; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.support.annotation.NonNull; import com.google.common.base.Preconditions; import java.util.List; import co.smartreceipts.android.model.Column; import co.smartreceipts.android.model.ColumnDefinitions; import co.smartreceipts.android.model.Receipt; import co.smartreceipts.android.persistence.database.defaults.TableDefaultsCustomizer; import co.smartreceipts.android.persistence.database.operations.DatabaseOperationMetadata; import co.smartreceipts.android.persistence.database.tables.adapters.ColumnDatabaseAdapter; import co.smartreceipts.android.persistence.database.tables.keys.ColumnPrimaryKey; import co.smartreceipts.android.utils.ListUtils; import co.smartreceipts.android.utils.log.Logger; import io.reactivex.Single; /** * Since our CSV and PDF tables share almost all of the same logic, this class purely acts as a wrapper around * each to centralize where all logic is managed */ public abstract class AbstractColumnTable extends AbstractSqlTable<Column<Receipt>, Integer> { private final int mTableExistsSinceDatabaseVersion; private final ColumnDefinitions<Receipt> mReceiptColumnDefinitions; private final String mIdColumnName; private final String mTypeColumnName; public AbstractColumnTable(@NonNull SQLiteOpenHelper sqLiteOpenHelper, @NonNull String tableName, int tableExistsSinceDatabaseVersion, @NonNull ColumnDefinitions<Receipt> receiptColumnDefinitions, @NonNull String idColumnName, @NonNull String typeColumnName) { super(sqLiteOpenHelper, tableName, new ColumnDatabaseAdapter(receiptColumnDefinitions, idColumnName, typeColumnName), new ColumnPrimaryKey(idColumnName)); mTableExistsSinceDatabaseVersion = tableExistsSinceDatabaseVersion; mReceiptColumnDefinitions = Preconditions.checkNotNull(receiptColumnDefinitions); mIdColumnName = Preconditions.checkNotNull(idColumnName); mTypeColumnName = Preconditions.checkNotNull(typeColumnName); } @Override public synchronized void onCreate(@NonNull SQLiteDatabase db, @NonNull TableDefaultsCustomizer customizer) { super.onCreate(db, customizer); final String columnsTable = "CREATE TABLE " + getTableName() + " (" + mIdColumnName + " INTEGER PRIMARY KEY AUTOINCREMENT, " + mTypeColumnName + " TEXT, " + AbstractSqlTable.COLUMN_DRIVE_SYNC_ID + " TEXT, " + AbstractSqlTable.COLUMN_DRIVE_IS_SYNCED + " BOOLEAN DEFAULT 0, " + AbstractSqlTable.COLUMN_DRIVE_MARKED_FOR_DELETION + " BOOLEAN DEFAULT 0, " + AbstractSqlTable.COLUMN_LAST_LOCAL_MODIFICATION_TIME + " DATE"+ ");"; Logger.debug(this, columnsTable); db.execSQL(columnsTable); insertDefaults(customizer); } @Override public synchronized void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion, @NonNull TableDefaultsCustomizer customizer) { super.onUpgrade(db, oldVersion, newVersion, customizer); if (oldVersion <= mTableExistsSinceDatabaseVersion) { final String columnsTable = "CREATE TABLE " + getTableName() + " (" + mIdColumnName + " INTEGER PRIMARY KEY AUTOINCREMENT, " + mTypeColumnName + " TEXT" + ");"; Logger.debug(this, columnsTable); db.execSQL(columnsTable); insertDefaults(customizer); } if (oldVersion <= 14) { onUpgradeToAddSyncInformation(db, oldVersion, newVersion); } } /** * Inserts the default column as defined by {@link ColumnDefinitions#getDefaultInsertColumn()} * * @return {@link Single} with the inserted {@link Column} or {@link Exception} if the insert failed */ @NonNull public final Single<Column<Receipt>> insertDefaultColumn() { return insert(mReceiptColumnDefinitions.getDefaultInsertColumn(), new DatabaseOperationMetadata()); } /** * Attempts to delete the last column in the list * * @return {@code true} if it could be delete. {@code false} otherwise (e.g. there are no more columns) */ @NonNull public final Single<Boolean> deleteLast(@NonNull final DatabaseOperationMetadata databaseOperationMetadata) { return get().flatMap(columns -> AbstractColumnTable.this.removeLastColumnIfPresent(columns, databaseOperationMetadata)); } /** * Passes alongs a call to insert our "table" defaults to the appropriate sub implementation * * @param customizer the {@link TableDefaultsCustomizer} implementation */ protected abstract void insertDefaults(@NonNull TableDefaultsCustomizer customizer); @NonNull private Single<Boolean> removeLastColumnIfPresent(@NonNull List<Column<Receipt>> columns, @NonNull DatabaseOperationMetadata databaseOperationMetadata) { final Column<Receipt> lastColumn = ListUtils.removeLast(columns); if (lastColumn != null) { return Single.just(deleteBlocking(lastColumn, databaseOperationMetadata) != null); } else { return Single.just(false); } } }