From 519e617dc79ba985510be01f6ea1e04dd3eadece Mon Sep 17 00:00:00 2001 From: M66B Date: Sat, 27 Jul 2019 18:48:47 +0200 Subject: [PATCH] Removed ROOM inline compilation --- app/build.gradle | 11 - .../IMultiInstanceInvalidationCallback.aidl | 33 - .../IMultiInstanceInvalidationService.aidl | 61 - .../androidx/room/DatabaseConfiguration.java | 294 ----- .../room/EntityDeletionOrUpdateAdapter.java | 115 -- .../androidx/room/EntityInsertionAdapter.java | 251 ---- .../room/InvalidationLiveDataContainer.java | 59 - .../androidx/room/InvalidationTracker.java | 853 ------------- .../room/MultiInstanceInvalidationClient.java | 200 ---- .../MultiInstanceInvalidationService.java | 134 --- app/src/main/java/androidx/room/Room.java | 109 -- .../main/java/androidx/room/RoomDatabase.java | 1065 ----------------- .../java/androidx/room/RoomOpenHelper.java | 277 ----- .../java/androidx/room/RoomSQLiteQuery.java | 299 ----- .../androidx/room/RoomTrackingLiveData.java | 169 --- .../androidx/room/SQLiteCopyOpenHelper.java | 205 ---- .../room/SQLiteCopyOpenHelperFactory.java | 56 - .../androidx/room/SharedSQLiteStatement.java | 100 -- .../androidx/room/TransactionExecutor.java | 62 - .../androidx/room/migration/Migration.java | 63 - .../main/java/androidx/room/package-info.java | 129 -- .../room/paging/LimitOffsetDataSource.java | 195 --- .../java/androidx/room/util/CopyLock.java | 112 -- .../java/androidx/room/util/CursorUtil.java | 113 -- .../main/java/androidx/room/util/DBUtil.java | 137 --- .../java/androidx/room/util/FileUtil.java | 71 -- .../java/androidx/room/util/FtsTableInfo.java | 220 ---- .../java/androidx/room/util/SneakyThrow.java | 47 - .../java/androidx/room/util/StringUtil.java | 118 -- .../java/androidx/room/util/TableInfo.java | 665 ---------- .../java/androidx/room/util/ViewInfo.java | 97 -- 31 files changed, 6320 deletions(-) delete mode 100644 app/src/main/aidl/androidx/room/IMultiInstanceInvalidationCallback.aidl delete mode 100644 app/src/main/aidl/androidx/room/IMultiInstanceInvalidationService.aidl delete mode 100644 app/src/main/java/androidx/room/DatabaseConfiguration.java delete mode 100644 app/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java delete mode 100644 app/src/main/java/androidx/room/EntityInsertionAdapter.java delete mode 100644 app/src/main/java/androidx/room/InvalidationLiveDataContainer.java delete mode 100644 app/src/main/java/androidx/room/InvalidationTracker.java delete mode 100644 app/src/main/java/androidx/room/MultiInstanceInvalidationClient.java delete mode 100644 app/src/main/java/androidx/room/MultiInstanceInvalidationService.java delete mode 100644 app/src/main/java/androidx/room/Room.java delete mode 100644 app/src/main/java/androidx/room/RoomDatabase.java delete mode 100644 app/src/main/java/androidx/room/RoomOpenHelper.java delete mode 100644 app/src/main/java/androidx/room/RoomSQLiteQuery.java delete mode 100644 app/src/main/java/androidx/room/RoomTrackingLiveData.java delete mode 100644 app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java delete mode 100644 app/src/main/java/androidx/room/SQLiteCopyOpenHelperFactory.java delete mode 100644 app/src/main/java/androidx/room/SharedSQLiteStatement.java delete mode 100644 app/src/main/java/androidx/room/TransactionExecutor.java delete mode 100644 app/src/main/java/androidx/room/migration/Migration.java delete mode 100644 app/src/main/java/androidx/room/package-info.java delete mode 100644 app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java delete mode 100644 app/src/main/java/androidx/room/util/CopyLock.java delete mode 100644 app/src/main/java/androidx/room/util/CursorUtil.java delete mode 100644 app/src/main/java/androidx/room/util/DBUtil.java delete mode 100644 app/src/main/java/androidx/room/util/FileUtil.java delete mode 100644 app/src/main/java/androidx/room/util/FtsTableInfo.java delete mode 100644 app/src/main/java/androidx/room/util/SneakyThrow.java delete mode 100644 app/src/main/java/androidx/room/util/StringUtil.java delete mode 100644 app/src/main/java/androidx/room/util/TableInfo.java delete mode 100644 app/src/main/java/androidx/room/util/ViewInfo.java diff --git a/app/build.gradle b/app/build.gradle index 2ef95f5970..e46d496c8b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,11 +40,6 @@ android { abortOnError false } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - packagingOptions { exclude 'META-INF/LICENSE.txt' exclude 'META-INF/README.md' @@ -137,9 +132,6 @@ repositories { } configurations.all { - // Workaround https://issuetracker.google.com/issues/138441698 - // Support @69c481c39a17d4e1e44a4eb298bb81c48f226eef - exclude group: "androidx.room", module: "room-runtime" // Workaround https://issuetracker.google.com/issues/134685570 exclude group: "androidx.lifecycle", module: "lifecycle-livedata" } @@ -204,9 +196,6 @@ dependencies { // https://mvnrepository.com/artifact/androidx.room/room-runtime implementation "androidx.room:room-runtime:$room_version" - implementation "androidx.room:room-common:$room_version" // because of exclude - // https://mvnrepository.com/artifact/androidx.sqlite/sqlite-framework - implementation "androidx.sqlite:sqlite-framework:2.0.1" // because of exclude annotationProcessor "androidx.room:room-compiler:$room_version" // https://mvnrepository.com/artifact/androidx.paging/paging-runtime diff --git a/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationCallback.aidl b/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationCallback.aidl deleted file mode 100644 index 7c702fff1b..0000000000 --- a/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationCallback.aidl +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -/** - * RPC Callbacks for {@link IMultiInstanceInvalidationService}. - * - * @hide - */ -interface IMultiInstanceInvalidationCallback { - - /** - * Called when invalidation is detected in another instance of the same database. - * - * @param tables List of invalidated table names - */ - oneway void onInvalidation(in String[] tables); - -} diff --git a/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationService.aidl b/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationService.aidl deleted file mode 100644 index 3b2c18c5ab..0000000000 --- a/app/src/main/aidl/androidx/room/IMultiInstanceInvalidationService.aidl +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import androidx.room.IMultiInstanceInvalidationCallback; - -/** - * RPC Service that controls interaction about multi-instance invalidation. - * - * @hide - */ -interface IMultiInstanceInvalidationService { - - /** - * Registers a new {@link IMultiInstanceInvalidationCallback} as a client of this service. - * - * @param callback The RPC callback. - * @param name The name of the database file as it is passed to {@link RoomDatabase.Builder}. - * @return A new client ID. The client needs to hold on to this ID and pass it to the service - * for subsequent calls. - */ - int registerCallback(IMultiInstanceInvalidationCallback callback, String name); - - /** - * Unregisters the specified {@link IMultiInstanceInvalidationCallback} from this service. - *

- * Clients might die without explicitly calling this method. In that case, the service should - * handle the clean up. - * - * @param callback The RPC callback. - * @param clientId The client ID returned from {@link #registerCallback}. - */ - void unregisterCallback(IMultiInstanceInvalidationCallback callback, int clientId); - - /** - * Broadcasts invalidation of database tables to other clients registered to this service. - *

- * The broadcast is delivered to {@link IMultiInstanceInvalidationCallback#onInvalidation} of - * the registered clients. The client calling this method will not receive its own broadcast. - * Clients that are associated with a different database file will not be notified. - * - * @param clientId The client ID returned from {@link #registerCallback}. - * @param tables The names of invalidated tables. - */ - oneway void broadcastInvalidation(int clientId, in String[] tables); - -} diff --git a/app/src/main/java/androidx/room/DatabaseConfiguration.java b/app/src/main/java/androidx/room/DatabaseConfiguration.java deleted file mode 100644 index f994445016..0000000000 --- a/app/src/main/java/androidx/room/DatabaseConfiguration.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.sqlite.db.SupportSQLiteOpenHelper; - -import java.io.File; -import java.util.List; -import java.util.Set; -import java.util.concurrent.Executor; - -/** - * Configuration class for a {@link RoomDatabase}. - */ -@SuppressWarnings("WeakerAccess") -public class DatabaseConfiguration { - - /** - * The factory to use to access the database. - */ - @NonNull - public final SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory; - /** - * The context to use while connecting to the database. - */ - @NonNull - public final Context context; - /** - * The name of the database file or null if it is an in-memory database. - */ - @Nullable - public final String name; - - /** - * Collection of available migrations. - */ - @NonNull - public final RoomDatabase.MigrationContainer migrationContainer; - - @Nullable - public final List callbacks; - - /** - * Whether Room should throw an exception for queries run on the main thread. - */ - public final boolean allowMainThreadQueries; - - /** - * The journal mode for this database. - */ - public final RoomDatabase.JournalMode journalMode; - - /** - * The Executor used to execute asynchronous queries. - */ - @NonNull - public final Executor queryExecutor; - - /** - * The Executor used to execute asynchronous transactions. - */ - @NonNull - public final Executor transactionExecutor; - - /** - * If true, table invalidation in an instance of {@link RoomDatabase} is broadcast and - * synchronized with other instances of the same {@link RoomDatabase} file, including those - * in a separate process. - */ - public final boolean multiInstanceInvalidation; - - /** - * If true, Room should crash if a migration is missing. - */ - public final boolean requireMigration; - - /** - * If true, Room should perform a destructive migration when downgrading without an available - * migration. - */ - public final boolean allowDestructiveMigrationOnDowngrade; - - /** - * The collection of schema versions from which migrations aren't required. - */ - private final Set mMigrationNotRequiredFrom; - - /** - * The assets path to a pre-packaged database to copy from. - */ - @Nullable - public final String copyFromAssetPath; - - /** - * The pre-packaged database file to copy from. - */ - @Nullable - public final File copyFromFile; - - - /** - * Creates a database configuration with the given values. - * - * @deprecated Use {@link #DatabaseConfiguration(Context, String, - * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean, - * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File)} - * - * @param context The application context. - * @param name Name of the database, can be null if it is in memory. - * @param sqliteOpenHelperFactory The open helper factory to use. - * @param migrationContainer The migration container for migrations. - * @param callbacks The list of callbacks for database events. - * @param allowMainThreadQueries Whether to allow main thread reads/writes or not. - * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING. - * @param queryExecutor The Executor used to execute asynchronous queries. - * @param requireMigration True if Room should require a valid migration if version changes, - * instead of recreating the tables. - * @param migrationNotRequiredFrom The collection of schema versions from which migrations - * aren't required. - * - * @hide - */ - @Deprecated - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public DatabaseConfiguration(@NonNull Context context, @Nullable String name, - @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory, - @NonNull RoomDatabase.MigrationContainer migrationContainer, - @Nullable List callbacks, - boolean allowMainThreadQueries, - RoomDatabase.JournalMode journalMode, - @NonNull Executor queryExecutor, - boolean requireMigration, - @Nullable Set migrationNotRequiredFrom) { - this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks, - allowMainThreadQueries, journalMode, queryExecutor, queryExecutor, false, - requireMigration, false, migrationNotRequiredFrom, null, null); - } - - /** - * Creates a database configuration with the given values. - * - * @deprecated Use {@link #DatabaseConfiguration(Context, String, - * SupportSQLiteOpenHelper.Factory, RoomDatabase.MigrationContainer, List, boolean, - * RoomDatabase.JournalMode, Executor, Executor, boolean, boolean, boolean, Set, String, File)} - * - * @param context The application context. - * @param name Name of the database, can be null if it is in memory. - * @param sqliteOpenHelperFactory The open helper factory to use. - * @param migrationContainer The migration container for migrations. - * @param callbacks The list of callbacks for database events. - * @param allowMainThreadQueries Whether to allow main thread reads/writes or not. - * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING. - * @param queryExecutor The Executor used to execute asynchronous queries. - * @param transactionExecutor The Executor used to execute asynchronous transactions. - * @param multiInstanceInvalidation True if Room should perform multi-instance invalidation. - * @param requireMigration True if Room should require a valid migration if version changes, - * @param allowDestructiveMigrationOnDowngrade True if Room should recreate tables if no - * migration is supplied during a downgrade. - * @param migrationNotRequiredFrom The collection of schema versions from which migrations - * aren't required. - * - * @hide - */ - @Deprecated - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public DatabaseConfiguration(@NonNull Context context, @Nullable String name, - @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory, - @NonNull RoomDatabase.MigrationContainer migrationContainer, - @Nullable List callbacks, - boolean allowMainThreadQueries, - RoomDatabase.JournalMode journalMode, - @NonNull Executor queryExecutor, - @NonNull Executor transactionExecutor, - boolean multiInstanceInvalidation, - boolean requireMigration, - boolean allowDestructiveMigrationOnDowngrade, - @Nullable Set migrationNotRequiredFrom) { - this(context, name, sqliteOpenHelperFactory, migrationContainer, callbacks, - allowMainThreadQueries, journalMode, queryExecutor, transactionExecutor, - multiInstanceInvalidation, requireMigration, allowDestructiveMigrationOnDowngrade, - migrationNotRequiredFrom, null, null); - } - - /** - * Creates a database configuration with the given values. - * - * @param context The application context. - * @param name Name of the database, can be null if it is in memory. - * @param sqliteOpenHelperFactory The open helper factory to use. - * @param migrationContainer The migration container for migrations. - * @param callbacks The list of callbacks for database events. - * @param allowMainThreadQueries Whether to allow main thread reads/writes or not. - * @param journalMode The journal mode. This has to be either TRUNCATE or WRITE_AHEAD_LOGGING. - * @param queryExecutor The Executor used to execute asynchronous queries. - * @param transactionExecutor The Executor used to execute asynchronous transactions. - * @param multiInstanceInvalidation True if Room should perform multi-instance invalidation. - * @param requireMigration True if Room should require a valid migration if version changes, - * @param allowDestructiveMigrationOnDowngrade True if Room should recreate tables if no - * migration is supplied during a downgrade. - * @param migrationNotRequiredFrom The collection of schema versions from which migrations - * aren't required. - * @param copyFromAssetPath The assets path to the pre-packaged database. - * @param copyFromFile The pre-packaged database file. - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public DatabaseConfiguration(@NonNull Context context, @Nullable String name, - @NonNull SupportSQLiteOpenHelper.Factory sqliteOpenHelperFactory, - @NonNull RoomDatabase.MigrationContainer migrationContainer, - @Nullable List callbacks, - boolean allowMainThreadQueries, - RoomDatabase.JournalMode journalMode, - @NonNull Executor queryExecutor, - @NonNull Executor transactionExecutor, - boolean multiInstanceInvalidation, - boolean requireMigration, - boolean allowDestructiveMigrationOnDowngrade, - @Nullable Set migrationNotRequiredFrom, - @Nullable String copyFromAssetPath, - @Nullable File copyFromFile) { - this.sqliteOpenHelperFactory = sqliteOpenHelperFactory; - this.context = context; - this.name = name; - this.migrationContainer = migrationContainer; - this.callbacks = callbacks; - this.allowMainThreadQueries = allowMainThreadQueries; - this.journalMode = journalMode; - this.queryExecutor = queryExecutor; - this.transactionExecutor = transactionExecutor; - this.multiInstanceInvalidation = multiInstanceInvalidation; - this.requireMigration = requireMigration; - this.allowDestructiveMigrationOnDowngrade = allowDestructiveMigrationOnDowngrade; - this.mMigrationNotRequiredFrom = migrationNotRequiredFrom; - this.copyFromAssetPath = copyFromAssetPath; - this.copyFromFile = copyFromFile; - } - - /** - * Returns whether a migration is required from the specified version. - * - * @param version The schema version. - * @return True if a valid migration is required, false otherwise. - * - * @deprecated Use {@link #isMigrationRequired(int, int)} which takes - * {@link #allowDestructiveMigrationOnDowngrade} into account. - */ - @Deprecated - public boolean isMigrationRequiredFrom(int version) { - return isMigrationRequired(version, version + 1); - } - - /** - * Returns whether a migration is required between two versions. - * - * @param fromVersion The old schema version. - * @param toVersion The new schema version. - * @return True if a valid migration is required, false otherwise. - */ - public boolean isMigrationRequired(int fromVersion, int toVersion) { - // Migrations are not required if its a downgrade AND destructive migration during downgrade - // has been allowed. - final boolean isDowngrade = fromVersion > toVersion; - if (isDowngrade && allowDestructiveMigrationOnDowngrade) { - return false; - } - - // Migrations are required between the two versions if we generally require migrations - // AND EITHER there are no exceptions OR the supplied fromVersion is not one of the - // exceptions. - return requireMigration - && (mMigrationNotRequiredFrom == null - || !mMigrationNotRequiredFrom.contains(fromVersion)); - } -} diff --git a/app/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java b/app/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java deleted file mode 100644 index 154103cfcb..0000000000 --- a/app/src/main/java/androidx/room/EntityDeletionOrUpdateAdapter.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import androidx.annotation.RestrictTo; -import androidx.sqlite.db.SupportSQLiteStatement; - -/** - * Implementations of this class knows how to delete or update a particular entity. - *

- * This is an internal library class and all of its implementations are auto-generated. - * - * @param The type parameter of the entity to be deleted - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -@SuppressWarnings({"WeakerAccess", "unused"}) -public abstract class EntityDeletionOrUpdateAdapter extends SharedSQLiteStatement { - /** - * Creates a DeletionOrUpdateAdapter that can delete or update the entity type T on the given - * database. - * - * @param database The database to delete / update the item in. - */ - public EntityDeletionOrUpdateAdapter(RoomDatabase database) { - super(database); - } - - /** - * Create the deletion or update query - * - * @return An SQL query that can delete or update instances of T. - */ - @Override - protected abstract String createQuery(); - - /** - * Binds the entity into the given statement. - * - * @param statement The SQLite statement that prepared for the query returned from - * createQuery. - * @param entity The entity of type T. - */ - protected abstract void bind(SupportSQLiteStatement statement, T entity); - - /** - * Deletes or updates the given entities in the database and returns the affected row count. - * - * @param entity The entity to delete or update - * @return The number of affected rows - */ - public final int handle(T entity) { - final SupportSQLiteStatement stmt = acquire(); - try { - bind(stmt, entity); - return stmt.executeUpdateDelete(); - } finally { - release(stmt); - } - } - - /** - * Deletes or updates the given entities in the database and returns the affected row count. - * - * @param entities Entities to delete or update - * @return The number of affected rows - */ - public final int handleMultiple(Iterable entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - int total = 0; - for (T entity : entities) { - bind(stmt, entity); - total += stmt.executeUpdateDelete(); - } - return total; - } finally { - release(stmt); - } - } - - /** - * Deletes or updates the given entities in the database and returns the affected row count. - * - * @param entities Entities to delete or update - * @return The number of affected rows - */ - public final int handleMultiple(T[] entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - int total = 0; - for (T entity : entities) { - bind(stmt, entity); - total += stmt.executeUpdateDelete(); - } - return total; - } finally { - release(stmt); - } - } -} diff --git a/app/src/main/java/androidx/room/EntityInsertionAdapter.java b/app/src/main/java/androidx/room/EntityInsertionAdapter.java deleted file mode 100644 index 3046d6cd0b..0000000000 --- a/app/src/main/java/androidx/room/EntityInsertionAdapter.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import androidx.annotation.RestrictTo; -import androidx.sqlite.db.SupportSQLiteStatement; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Implementations of this class knows how to insert a particular entity. - *

- * This is an internal library class and all of its implementations are auto-generated. - * - * @param The type parameter of the entity to be inserted - * @hide - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public abstract class EntityInsertionAdapter extends SharedSQLiteStatement { - /** - * Creates an InsertionAdapter that can insert the entity type T into the given database. - * - * @param database The database to insert into. - */ - public EntityInsertionAdapter(RoomDatabase database) { - super(database); - } - - /** - * Binds the entity into the given statement. - * - * @param statement The SQLite statement that prepared for the query returned from - * createInsertQuery. - * @param entity The entity of type T. - */ - protected abstract void bind(SupportSQLiteStatement statement, T entity); - - /** - * Inserts the entity into the database. - * - * @param entity The entity to insert - */ - public final void insert(T entity) { - final SupportSQLiteStatement stmt = acquire(); - try { - bind(stmt, entity); - stmt.executeInsert(); - } finally { - release(stmt); - } - } - - /** - * Inserts the given entities into the database. - * - * @param entities Entities to insert - */ - public final void insert(T[] entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - for (T entity : entities) { - bind(stmt, entity); - stmt.executeInsert(); - } - } finally { - release(stmt); - } - } - - /** - * Inserts the given entities into the database. - * - * @param entities Entities to insert - */ - public final void insert(Iterable entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - for (T entity : entities) { - bind(stmt, entity); - stmt.executeInsert(); - } - } finally { - release(stmt); - } - } - - /** - * Inserts the given entity into the database and returns the row id. - * - * @param entity The entity to insert - * @return The SQLite row id or -1 if no row is inserted - */ - public final long insertAndReturnId(T entity) { - final SupportSQLiteStatement stmt = acquire(); - try { - bind(stmt, entity); - return stmt.executeInsert(); - } finally { - release(stmt); - } - } - - /** - * Inserts the given entities into the database and returns the row ids. - * - * @param entities Entities to insert - * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1 - */ - public final long[] insertAndReturnIdsArray(Collection entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - final long[] result = new long[entities.size()]; - int index = 0; - for (T entity : entities) { - bind(stmt, entity); - result[index] = stmt.executeInsert(); - index++; - } - return result; - } finally { - release(stmt); - } - } - - /** - * Inserts the given entities into the database and returns the row ids. - * - * @param entities Entities to insert - * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1 - */ - public final long[] insertAndReturnIdsArray(T[] entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - final long[] result = new long[entities.length]; - int index = 0; - for (T entity : entities) { - bind(stmt, entity); - result[index] = stmt.executeInsert(); - index++; - } - return result; - } finally { - release(stmt); - } - } - - /** - * Inserts the given entities into the database and returns the row ids. - * - * @param entities Entities to insert - * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1 - */ - public final Long[] insertAndReturnIdsArrayBox(Collection entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - final Long[] result = new Long[entities.size()]; - int index = 0; - for (T entity : entities) { - bind(stmt, entity); - result[index] = stmt.executeInsert(); - index++; - } - return result; - } finally { - release(stmt); - } - } - - /** - * Inserts the given entities into the database and returns the row ids. - * - * @param entities Entities to insert - * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1 - */ - public final Long[] insertAndReturnIdsArrayBox(T[] entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - final Long[] result = new Long[entities.length]; - int index = 0; - for (T entity : entities) { - bind(stmt, entity); - result[index] = stmt.executeInsert(); - index++; - } - return result; - } finally { - release(stmt); - } - } - - /** - * Inserts the given entities into the database and returns the row ids. - * - * @param entities Entities to insert - * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1 - */ - public final List insertAndReturnIdsList(T[] entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - final List result = new ArrayList<>(entities.length); - int index = 0; - for (T entity : entities) { - bind(stmt, entity); - result.add(index, stmt.executeInsert()); - index++; - } - return result; - } finally { - release(stmt); - } - } - - /** - * Inserts the given entities into the database and returns the row ids. - * - * @param entities Entities to insert - * @return The SQLite row ids, for entities that are not inserted the row id returned will be -1 - */ - public final List insertAndReturnIdsList(Collection entities) { - final SupportSQLiteStatement stmt = acquire(); - try { - final List result = new ArrayList<>(entities.size()); - int index = 0; - for (T entity : entities) { - bind(stmt, entity); - result.add(index, stmt.executeInsert()); - index++; - } - return result; - } finally { - release(stmt); - } - } -} diff --git a/app/src/main/java/androidx/room/InvalidationLiveDataContainer.java b/app/src/main/java/androidx/room/InvalidationLiveDataContainer.java deleted file mode 100644 index e1e8155f8a..0000000000 --- a/app/src/main/java/androidx/room/InvalidationLiveDataContainer.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import androidx.annotation.VisibleForTesting; -import androidx.lifecycle.LiveData; - -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.Set; -import java.util.concurrent.Callable; - -/** - * A helper class that maintains {@link RoomTrackingLiveData} instances for an - * {@link InvalidationTracker}. - *

- * We keep a strong reference to active LiveData instances to avoid garbage collection in case - * developer does not hold onto the returned LiveData. - */ -class InvalidationLiveDataContainer { - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - final Set mLiveDataSet = Collections.newSetFromMap( - new IdentityHashMap() - ); - private final RoomDatabase mDatabase; - - InvalidationLiveDataContainer(RoomDatabase database) { - mDatabase = database; - } - - LiveData create(String[] tableNames, boolean inTransaction, - Callable computeFunction) { - return new RoomTrackingLiveData<>(mDatabase, this, inTransaction, computeFunction, - tableNames); - } - - void onActive(LiveData liveData) { - mLiveDataSet.add(liveData); - } - - void onInactive(LiveData liveData) { - mLiveDataSet.remove(liveData); - } -} diff --git a/app/src/main/java/androidx/room/InvalidationTracker.java b/app/src/main/java/androidx/room/InvalidationTracker.java deleted file mode 100644 index f35b7f9550..0000000000 --- a/app/src/main/java/androidx/room/InvalidationTracker.java +++ /dev/null @@ -1,853 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.annotation.VisibleForTesting; -import androidx.annotation.WorkerThread; -import androidx.arch.core.internal.SafeIterableMap; -import androidx.lifecycle.LiveData; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.sqlite.db.SupportSQLiteStatement; - -import java.lang.ref.WeakReference; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Lock; - -/** - * InvalidationTracker keeps a list of tables modified by queries and notifies its callbacks about - * these tables. - */ -// Some details on how the InvalidationTracker works: -// * An in memory table is created with (table_id, invalidated) table_id is a hardcoded int from -// initialization, while invalidated is a boolean bit to indicate if the table has been invalidated. -// * ObservedTableTracker tracks list of tables we should be watching (e.g. adding triggers for). -// * Before each beginTransaction, RoomDatabase invokes InvalidationTracker to sync trigger states. -// * After each endTransaction, RoomDatabase invokes InvalidationTracker to refresh invalidated -// tables. -// * Each update (write operation) on one of the observed tables triggers an update into the -// memory table table, flipping the invalidated flag ON. -// * When multi-instance invalidation is turned on, MultiInstanceInvalidationClient will be created. -// It works as an Observer, and notifies other instances of table invalidation. -public class InvalidationTracker { - - private static final String[] TRIGGERS = new String[]{"UPDATE", "DELETE", "INSERT"}; - - private static final String UPDATE_TABLE_NAME = "room_table_modification_log"; - - private static final String TABLE_ID_COLUMN_NAME = "table_id"; - - private static final String INVALIDATED_COLUMN_NAME = "invalidated"; - - private static final String CREATE_TRACKING_TABLE_SQL = "CREATE TEMP TABLE " + UPDATE_TABLE_NAME - + "(" + TABLE_ID_COLUMN_NAME + " INTEGER PRIMARY KEY, " - + INVALIDATED_COLUMN_NAME + " INTEGER NOT NULL DEFAULT 0)"; - - @VisibleForTesting - static final String RESET_UPDATED_TABLES_SQL = "UPDATE " + UPDATE_TABLE_NAME - + " SET " + INVALIDATED_COLUMN_NAME + " = 0 WHERE " + INVALIDATED_COLUMN_NAME + " = 1 "; - - @VisibleForTesting - static final String SELECT_UPDATED_TABLES_SQL = "SELECT * FROM " + UPDATE_TABLE_NAME - + " WHERE " + INVALIDATED_COLUMN_NAME + " = 1;"; - - @NonNull - @VisibleForTesting - final HashMap mTableIdLookup; - final String[] mTableNames; - - @NonNull - private Map> mViewTables; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - final RoomDatabase mDatabase; - - AtomicBoolean mPendingRefresh = new AtomicBoolean(false); - - private volatile boolean mInitialized = false; - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - volatile SupportSQLiteStatement mCleanupStatement; - - private ObservedTableTracker mObservedTableTracker; - - private final InvalidationLiveDataContainer mInvalidationLiveDataContainer; - - // should be accessed with synchronization only. - @VisibleForTesting - @SuppressLint("RestrictedApi") - final SafeIterableMap mObserverMap = new SafeIterableMap<>(); - - private MultiInstanceInvalidationClient mMultiInstanceInvalidationClient; - - /** - * Used by the generated code. - * - * @hide - */ - @SuppressWarnings("WeakerAccess") - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public InvalidationTracker(RoomDatabase database, String... tableNames) { - this(database, new HashMap(), Collections.>emptyMap(), - tableNames); - } - - /** - * Used by the generated code. - * - * @hide - */ - @SuppressWarnings("WeakerAccess") - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public InvalidationTracker(RoomDatabase database, Map shadowTablesMap, - Map> viewTables, String... tableNames) { - mDatabase = database; - mObservedTableTracker = new ObservedTableTracker(tableNames.length); - mTableIdLookup = new HashMap<>(); - mViewTables = viewTables; - mInvalidationLiveDataContainer = new InvalidationLiveDataContainer(mDatabase); - final int size = tableNames.length; - mTableNames = new String[size]; - for (int id = 0; id < size; id++) { - final String tableName = tableNames[id].toLowerCase(Locale.US); - mTableIdLookup.put(tableName, id); - String shadowTableName = shadowTablesMap.get(tableNames[id]); - if (shadowTableName != null) { - mTableNames[id] = shadowTableName.toLowerCase(Locale.US); - } else { - mTableNames[id] = tableName; - } - } - // Adjust table id lookup for those tables whose shadow table is another already mapped - // table (e.g. external content fts tables). - for (Map.Entry shadowTableEntry : shadowTablesMap.entrySet()) { - String shadowTableName = shadowTableEntry.getValue().toLowerCase(Locale.US); - if (mTableIdLookup.containsKey(shadowTableName)) { - String tableName = shadowTableEntry.getKey().toLowerCase(Locale.US); - mTableIdLookup.put(tableName, mTableIdLookup.get(shadowTableName)); - } - } - } - - /** - * Internal method to initialize table tracking. - *

- * You should never call this method, it is called by the generated code. - */ - void internalInit(SupportSQLiteDatabase database) { - synchronized (this) { - if (mInitialized) { - Log.e(Room.LOG_TAG, "Invalidation tracker is initialized twice :/."); - return; - } - - // These actions are not in a transaction because temp_store is not allowed to be - // performed on a transaction, and recursive_triggers is not affected by transactions. - database.execSQL("PRAGMA temp_store = MEMORY;"); - database.execSQL("PRAGMA recursive_triggers='ON';"); - database.execSQL(CREATE_TRACKING_TABLE_SQL); - syncTriggers(database); - mCleanupStatement = database.compileStatement(RESET_UPDATED_TABLES_SQL); - mInitialized = true; - } - } - - void startMultiInstanceInvalidation(Context context, String name) { - mMultiInstanceInvalidationClient = new MultiInstanceInvalidationClient(context, name, this, - mDatabase.getQueryExecutor()); - } - - void stopMultiInstanceInvalidation() { - if (mMultiInstanceInvalidationClient != null) { - mMultiInstanceInvalidationClient.stop(); - mMultiInstanceInvalidationClient = null; - } - } - - private static void appendTriggerName(StringBuilder builder, String tableName, - String triggerType) { - builder.append("`") - .append("room_table_modification_trigger_") - .append(tableName) - .append("_") - .append(triggerType) - .append("`"); - } - - private void stopTrackingTable(SupportSQLiteDatabase writableDb, int tableId) { - final String tableName = mTableNames[tableId]; - StringBuilder stringBuilder = new StringBuilder(); - for (String trigger : TRIGGERS) { - stringBuilder.setLength(0); - stringBuilder.append("DROP TRIGGER IF EXISTS "); - appendTriggerName(stringBuilder, tableName, trigger); - writableDb.execSQL(stringBuilder.toString()); - } - } - - private void startTrackingTable(SupportSQLiteDatabase writableDb, int tableId) { - writableDb.execSQL( - "INSERT OR IGNORE INTO " + UPDATE_TABLE_NAME + " VALUES(" + tableId + ", 0)"); - final String tableName = mTableNames[tableId]; - StringBuilder stringBuilder = new StringBuilder(); - for (String trigger : TRIGGERS) { - stringBuilder.setLength(0); - stringBuilder.append("CREATE TEMP TRIGGER IF NOT EXISTS "); - appendTriggerName(stringBuilder, tableName, trigger); - stringBuilder.append(" AFTER ") - .append(trigger) - .append(" ON `") - .append(tableName) - .append("` BEGIN UPDATE ") - .append(UPDATE_TABLE_NAME) - .append(" SET ").append(INVALIDATED_COLUMN_NAME).append(" = 1") - .append(" WHERE ").append(TABLE_ID_COLUMN_NAME).append(" = ").append(tableId) - .append(" AND ").append(INVALIDATED_COLUMN_NAME).append(" = 0") - .append("; END"); - writableDb.execSQL(stringBuilder.toString()); - } - } - - /** - * Adds the given observer to the observers list and it will be notified if any table it - * observes changes. - *

- * Database changes are pulled on another thread so in some race conditions, the observer might - * be invoked for changes that were done before it is added. - *

- * If the observer already exists, this is a no-op call. - *

- * If one of the tables in the Observer does not exist in the database, this method throws an - * {@link IllegalArgumentException}. - * - * @param observer The observer which listens the database for changes. - */ - @SuppressLint("RestrictedApi") - @WorkerThread - public void addObserver(@NonNull Observer observer) { - final String[] tableNames = resolveViews(observer.mTables); - int[] tableIds = new int[tableNames.length]; - final int size = tableNames.length; - - for (int i = 0; i < size; i++) { - Integer tableId = mTableIdLookup.get(tableNames[i].toLowerCase(Locale.US)); - if (tableId == null) { - throw new IllegalArgumentException("There is no table with name " + tableNames[i]); - } - tableIds[i] = tableId; - } - ObserverWrapper wrapper = new ObserverWrapper(observer, tableIds, tableNames); - ObserverWrapper currentObserver; - synchronized (mObserverMap) { - currentObserver = mObserverMap.putIfAbsent(observer, wrapper); - } - if (currentObserver == null && mObservedTableTracker.onAdded(tableIds)) { - syncTriggers(); - } - } - - private String[] validateAndResolveTableNames(String[] tableNames) { - String[] resolved = resolveViews(tableNames); - for (String tableName : resolved) { - if (!mTableIdLookup.containsKey(tableName.toLowerCase(Locale.US))) { - throw new IllegalArgumentException("There is no table with name " + tableName); - } - } - return resolved; - } - - /** - * Resolves the list of tables and views into a list of unique tables that are underlying them. - * - * @param names The names of tables or views. - * @return The names of the underlying tables. - */ - private String[] resolveViews(String[] names) { - Set tables = new HashSet<>(); - for (String name : names) { - final String lowercase = name.toLowerCase(Locale.US); - if (mViewTables.containsKey(lowercase)) { - tables.addAll(mViewTables.get(lowercase)); - } else { - tables.add(name); - } - } - return tables.toArray(new String[tables.size()]); - } - - /** - * Adds an observer but keeps a weak reference back to it. - *

- * Note that you cannot remove this observer once added. It will be automatically removed - * when the observer is GC'ed. - * - * @param observer The observer to which InvalidationTracker will keep a weak reference. - * @hide - */ - @SuppressWarnings("unused") - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public void addWeakObserver(Observer observer) { - addObserver(new WeakObserver(this, observer)); - } - - /** - * Removes the observer from the observers list. - * - * @param observer The observer to remove. - */ - @SuppressLint("RestrictedApi") - @SuppressWarnings("WeakerAccess") - @WorkerThread - public void removeObserver(@NonNull final Observer observer) { - ObserverWrapper wrapper; - synchronized (mObserverMap) { - wrapper = mObserverMap.remove(observer); - } - if (wrapper != null && mObservedTableTracker.onRemoved(wrapper.mTableIds)) { - syncTriggers(); - } - } - - @SuppressWarnings("WeakerAccess") /* synthetic access */ - boolean ensureInitialization() { - if (!mDatabase.isOpen()) { - return false; - } - if (!mInitialized) { - // trigger initialization - mDatabase.getOpenHelper().getWritableDatabase(); - } - if (!mInitialized) { - Log.e(Room.LOG_TAG, "database is not initialized even though it is open"); - return false; - } - return true; - } - - @VisibleForTesting - Runnable mRefreshRunnable = new Runnable() { - @Override - public void run() { - final Lock closeLock = mDatabase.getCloseLock(); - Set invalidatedTableIds = null; - try { - closeLock.lock(); - - if (!ensureInitialization()) { - return; - } - - if (!mPendingRefresh.compareAndSet(true, false)) { - // no pending refresh - return; - } - - if (mDatabase.inTransaction()) { - // current thread is in a transaction. when it ends, it will invoke - // refreshRunnable again. mPendingRefresh is left as false on purpose - // so that the last transaction can flip it on again. - return; - } - - if (mDatabase.mWriteAheadLoggingEnabled) { - // This transaction has to be on the underlying DB rather than the RoomDatabase - // in order to avoid a recursive loop after endTransaction. - SupportSQLiteDatabase db = mDatabase.getOpenHelper().getWritableDatabase(); - db.beginTransaction(); - try { - invalidatedTableIds = checkUpdatedTable(); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - } else { - invalidatedTableIds = checkUpdatedTable(); - } - } catch (IllegalStateException | SQLiteException exception) { - // may happen if db is closed. just log. - Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?", - exception); - } finally { - closeLock.unlock(); - } - if (invalidatedTableIds != null && !invalidatedTableIds.isEmpty()) { - synchronized (mObserverMap) { - for (Map.Entry entry : mObserverMap) { - entry.getValue().notifyByTableInvalidStatus(invalidatedTableIds); - } - } - } - } - - private Set checkUpdatedTable() { - HashSet invalidatedTableIds = new HashSet<>(); - Cursor cursor = mDatabase.query(new SimpleSQLiteQuery(SELECT_UPDATED_TABLES_SQL)); - //noinspection TryFinallyCanBeTryWithResources - try { - while (cursor.moveToNext()) { - final int tableId = cursor.getInt(0); - invalidatedTableIds.add(tableId); - } - } finally { - cursor.close(); - } - if (!invalidatedTableIds.isEmpty()) { - mCleanupStatement.executeUpdateDelete(); - } - return invalidatedTableIds; - } - }; - - /** - * Enqueues a task to refresh the list of updated tables. - *

- * This method is automatically called when {@link RoomDatabase#endTransaction()} is called but - * if you have another connection to the database or directly use {@link - * SupportSQLiteDatabase}, you may need to call this manually. - */ - @SuppressWarnings("WeakerAccess") - public void refreshVersionsAsync() { - // TODO we should consider doing this sync instead of async. - if (mPendingRefresh.compareAndSet(false, true)) { - mDatabase.getQueryExecutor().execute(mRefreshRunnable); - } - } - - /** - * Check versions for tables, and run observers synchronously if tables have been updated. - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - @WorkerThread - public void refreshVersionsSync() { - syncTriggers(); - mRefreshRunnable.run(); - } - - /** - * Notifies all the registered {@link Observer}s of table changes. - *

- * This can be used for notifying invalidation that cannot be detected by this - * {@link InvalidationTracker}, for example, invalidation from another process. - * - * @param tables The invalidated tables. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY) - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) - public void notifyObserversByTableNames(String... tables) { - synchronized (mObserverMap) { - for (Map.Entry entry : mObserverMap) { - if (!entry.getKey().isRemote()) { - entry.getValue().notifyByTableNames(tables); - } - } - } - } - - void syncTriggers(SupportSQLiteDatabase database) { - if (database.inTransaction()) { - // we won't run this inside another transaction. - return; - } - try { - // This method runs in a while loop because while changes are synced to db, another - // runnable may be skipped. If we cause it to skip, we need to do its work. - while (true) { - Lock closeLock = mDatabase.getCloseLock(); - closeLock.lock(); - try { - // there is a potential race condition where another mSyncTriggers runnable - // can start running right after we get the tables list to sync. - final int[] tablesToSync = mObservedTableTracker.getTablesToSync(); - if (tablesToSync == null) { - return; - } - final int limit = tablesToSync.length; - database.beginTransaction(); - try { - for (int tableId = 0; tableId < limit; tableId++) { - switch (tablesToSync[tableId]) { - case ObservedTableTracker.ADD: - startTrackingTable(database, tableId); - break; - case ObservedTableTracker.REMOVE: - stopTrackingTable(database, tableId); - break; - } - } - database.setTransactionSuccessful(); - } finally { - database.endTransaction(); - } - mObservedTableTracker.onSyncCompleted(); - } finally { - closeLock.unlock(); - } - } - } catch (IllegalStateException | SQLiteException exception) { - // may happen if db is closed. just log. - Log.e(Room.LOG_TAG, "Cannot run invalidation tracker. Is the db closed?", - exception); - } - } - - /** - * Called by RoomDatabase before each beginTransaction call. - *

- * It is important that pending trigger changes are applied to the database before any query - * runs. Otherwise, we may miss some changes. - *

- * This api should eventually be public. - */ - void syncTriggers() { - if (!mDatabase.isOpen()) { - return; - } - syncTriggers(mDatabase.getOpenHelper().getWritableDatabase()); - } - - /** - * Creates a LiveData that computes the given function once and for every other invalidation - * of the database. - *

- * Holds a strong reference to the created LiveData as long as it is active. - * - * @deprecated Use {@link #createLiveData(String[], boolean, Callable)} - * - * @param computeFunction The function that calculates the value - * @param tableNames The list of tables to observe - * @param The return type - * @return A new LiveData that computes the given function when the given list of tables - * invalidates. - * @hide - */ - @Deprecated - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public LiveData createLiveData(String[] tableNames, Callable computeFunction) { - return createLiveData(tableNames, false, computeFunction); - } - - /** - * Creates a LiveData that computes the given function once and for every other invalidation - * of the database. - *

- * Holds a strong reference to the created LiveData as long as it is active. - * - * @param tableNames The list of tables to observe - * @param inTransaction True if the computeFunction will be done in a transaction, false - * otherwise. - * @param computeFunction The function that calculates the value - * @param The return type - * @return A new LiveData that computes the given function when the given list of tables - * invalidates. - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public LiveData createLiveData(String[] tableNames, boolean inTransaction, - Callable computeFunction) { - return mInvalidationLiveDataContainer.create( - validateAndResolveTableNames(tableNames), inTransaction, computeFunction); - } - - /** - * Wraps an observer and keeps the table information. - *

- * Internally table ids are used which may change from database to database so the table - * related information is kept here rather than in the Observer. - */ - @SuppressWarnings("WeakerAccess") - static class ObserverWrapper { - final int[] mTableIds; - private final String[] mTableNames; - final Observer mObserver; - private final Set mSingleTableSet; - - ObserverWrapper(Observer observer, int[] tableIds, String[] tableNames) { - mObserver = observer; - mTableIds = tableIds; - mTableNames = tableNames; - if (tableIds.length == 1) { - HashSet set = new HashSet<>(); - set.add(mTableNames[0]); - mSingleTableSet = Collections.unmodifiableSet(set); - } else { - mSingleTableSet = null; - } - } - - /** - * Notifies the underlying {@link #mObserver} if any of the observed tables are invalidated - * based on the given invalid status set. - * - * @param invalidatedTablesIds The table ids of the tables that are invalidated. - */ - void notifyByTableInvalidStatus(Set invalidatedTablesIds) { - Set invalidatedTables = null; - final int size = mTableIds.length; - for (int index = 0; index < size; index++) { - final int tableId = mTableIds[index]; - if (invalidatedTablesIds.contains(tableId)) { - if (size == 1) { - // Optimization for a single-table observer - invalidatedTables = mSingleTableSet; - } else { - if (invalidatedTables == null) { - invalidatedTables = new HashSet<>(size); - } - invalidatedTables.add(mTableNames[index]); - } - } - } - if (invalidatedTables != null) { - mObserver.onInvalidated(invalidatedTables); - } - } - - /** - * Notifies the underlying {@link #mObserver} if it observes any of the specified - * {@code tables}. - * - * @param tables The invalidated table names. - */ - void notifyByTableNames(String[] tables) { - Set invalidatedTables = null; - if (mTableNames.length == 1) { - for (String table : tables) { - if (table.equalsIgnoreCase(mTableNames[0])) { - // Optimization for a single-table observer - invalidatedTables = mSingleTableSet; - break; - } - } - } else { - HashSet set = new HashSet<>(); - for (String table : tables) { - for (String ourTable : mTableNames) { - if (ourTable.equalsIgnoreCase(table)) { - set.add(ourTable); - break; - } - } - } - if (set.size() > 0) { - invalidatedTables = set; - } - } - if (invalidatedTables != null) { - mObserver.onInvalidated(invalidatedTables); - } - } - } - - /** - * An observer that can listen for changes in the database. - */ - public abstract static class Observer { - final String[] mTables; - - /** - * Observes the given list of tables and views. - * - * @param firstTable The name of the table or view. - * @param rest More names of tables or views. - */ - @SuppressWarnings("unused") - protected Observer(@NonNull String firstTable, String... rest) { - mTables = Arrays.copyOf(rest, rest.length + 1); - mTables[rest.length] = firstTable; - } - - /** - * Observes the given list of tables and views. - * - * @param tables The list of tables or views to observe for changes. - */ - public Observer(@NonNull String[] tables) { - // copy tables in case user modifies them afterwards - mTables = Arrays.copyOf(tables, tables.length); - } - - /** - * Called when one of the observed tables is invalidated in the database. - * - * @param tables A set of invalidated tables. This is useful when the observer targets - * multiple tables and you want to know which table is invalidated. This will - * be names of underlying tables when you are observing views. - */ - public abstract void onInvalidated(@NonNull Set tables); - - boolean isRemote() { - return false; - } - } - - /** - * Keeps a list of tables we should observe. Invalidation tracker lazily syncs this list w/ - * triggers in the database. - *

- * This class is thread safe - */ - static class ObservedTableTracker { - static final int NO_OP = 0; // don't change trigger state for this table - static final int ADD = 1; // add triggers for this table - static final int REMOVE = 2; // remove triggers for this table - - // number of observers per table - final long[] mTableObservers; - // trigger state for each table at last sync - // this field is updated when syncAndGet is called. - final boolean[] mTriggerStates; - // when sync is called, this field is returned. It includes actions as ADD, REMOVE, NO_OP - final int[] mTriggerStateChanges; - - boolean mNeedsSync; - - /** - * After we return non-null value from getTablesToSync, we expect a onSyncCompleted before - * returning any non-null value from getTablesToSync. - * This allows us to workaround any multi-threaded state syncing issues. - */ - boolean mPendingSync; - - ObservedTableTracker(int tableCount) { - mTableObservers = new long[tableCount]; - mTriggerStates = new boolean[tableCount]; - mTriggerStateChanges = new int[tableCount]; - Arrays.fill(mTableObservers, 0); - Arrays.fill(mTriggerStates, false); - } - - /** - * @return true if # of triggers is affected. - */ - boolean onAdded(int... tableIds) { - boolean needTriggerSync = false; - synchronized (this) { - for (int tableId : tableIds) { - final long prevObserverCount = mTableObservers[tableId]; - mTableObservers[tableId] = prevObserverCount + 1; - if (prevObserverCount == 0) { - mNeedsSync = true; - needTriggerSync = true; - } - } - } - return needTriggerSync; - } - - /** - * @return true if # of triggers is affected. - */ - boolean onRemoved(int... tableIds) { - boolean needTriggerSync = false; - synchronized (this) { - for (int tableId : tableIds) { - final long prevObserverCount = mTableObservers[tableId]; - mTableObservers[tableId] = prevObserverCount - 1; - if (prevObserverCount == 1) { - mNeedsSync = true; - needTriggerSync = true; - } - } - } - return needTriggerSync; - } - - /** - * If this returns non-null, you must call onSyncCompleted. - * - * @return int[] An int array where the index for each tableId has the action for that - * table. - */ - @Nullable - int[] getTablesToSync() { - synchronized (this) { - if (!mNeedsSync || mPendingSync) { - return null; - } - final int tableCount = mTableObservers.length; - for (int i = 0; i < tableCount; i++) { - final boolean newState = mTableObservers[i] > 0; - if (newState != mTriggerStates[i]) { - mTriggerStateChanges[i] = newState ? ADD : REMOVE; - } else { - mTriggerStateChanges[i] = NO_OP; - } - mTriggerStates[i] = newState; - } - mPendingSync = true; - mNeedsSync = false; - return mTriggerStateChanges; - } - } - - /** - * if getTablesToSync returned non-null, the called should call onSyncCompleted once it - * is done. - */ - void onSyncCompleted() { - synchronized (this) { - mPendingSync = false; - } - } - } - - /** - * An Observer wrapper that keeps a weak reference to the given object. - *

- * This class will automatically unsubscribe when the wrapped observer goes out of memory. - */ - static class WeakObserver extends Observer { - final InvalidationTracker mTracker; - final WeakReference mDelegateRef; - - WeakObserver(InvalidationTracker tracker, Observer delegate) { - super(delegate.mTables); - mTracker = tracker; - mDelegateRef = new WeakReference<>(delegate); - } - - @Override - public void onInvalidated(@NonNull Set tables) { - final Observer observer = mDelegateRef.get(); - if (observer == null) { - mTracker.removeObserver(this); - } else { - observer.onInvalidated(tables); - } - } - } -} diff --git a/app/src/main/java/androidx/room/MultiInstanceInvalidationClient.java b/app/src/main/java/androidx/room/MultiInstanceInvalidationClient.java deleted file mode 100644 index 3aeddcc66a..0000000000 --- a/app/src/main/java/androidx/room/MultiInstanceInvalidationClient.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Set; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Handles all the communication from {@link RoomDatabase} and {@link InvalidationTracker} to - * {@link MultiInstanceInvalidationService}. - */ -class MultiInstanceInvalidationClient { - - /** - * The application context. - */ - // synthetic access - @SuppressWarnings("WeakerAccess") - final Context mAppContext; - - /** - * The name of the database file. - */ - // synthetic access - @SuppressWarnings("WeakerAccess") - final String mName; - - /** - * The client ID assigned by {@link MultiInstanceInvalidationService}. - */ - // synthetic access - @SuppressWarnings("WeakerAccess") - int mClientId; - - // synthetic access - @SuppressWarnings("WeakerAccess") - final InvalidationTracker mInvalidationTracker; - - // synthetic access - @SuppressWarnings("WeakerAccess") - final InvalidationTracker.Observer mObserver; - - // synthetic access - @SuppressWarnings("WeakerAccess") - @Nullable - IMultiInstanceInvalidationService mService; - - // synthetic access - @SuppressWarnings("WeakerAccess") - final Executor mExecutor; - - // synthetic access - @SuppressWarnings("WeakerAccess") - final IMultiInstanceInvalidationCallback mCallback = - new IMultiInstanceInvalidationCallback.Stub() { - @Override - public void onInvalidation(final String[] tables) { - mExecutor.execute(new Runnable() { - @Override - public void run() { - mInvalidationTracker.notifyObserversByTableNames(tables); - } - }); - } - }; - - // synthetic access - @SuppressWarnings("WeakerAccess") - final AtomicBoolean mStopped = new AtomicBoolean(false); - - // synthetic access - @SuppressWarnings("WeakerAccess") - final ServiceConnection mServiceConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - mService = IMultiInstanceInvalidationService.Stub.asInterface(service); - mExecutor.execute(mSetUpRunnable); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mExecutor.execute(mRemoveObserverRunnable); - mService = null; - } - - }; - - // synthetic access - @SuppressWarnings("WeakerAccess") - final Runnable mSetUpRunnable = new Runnable() { - @Override - public void run() { - try { - final IMultiInstanceInvalidationService service = mService; - if (service != null) { - mClientId = service.registerCallback(mCallback, mName); - mInvalidationTracker.addObserver(mObserver); - } - } catch (RemoteException e) { - Log.w(Room.LOG_TAG, "Cannot register multi-instance invalidation callback", e); - } - } - }; - - // synthetic access - @SuppressWarnings("WeakerAccess") - final Runnable mRemoveObserverRunnable = new Runnable() { - @Override - public void run() { - mInvalidationTracker.removeObserver(mObserver); - } - }; - - private final Runnable mTearDownRunnable = new Runnable() { - @Override - public void run() { - mInvalidationTracker.removeObserver(mObserver); - try { - final IMultiInstanceInvalidationService service = mService; - if (service != null) { - service.unregisterCallback(mCallback, mClientId); - } - } catch (RemoteException e) { - Log.w(Room.LOG_TAG, "Cannot unregister multi-instance invalidation callback", e); - } - mAppContext.unbindService(mServiceConnection); - } - }; - - /** - * @param context The Context to be used for binding - * {@link IMultiInstanceInvalidationService}. - * @param name The name of the database file. - * @param invalidationTracker The {@link InvalidationTracker} - * @param executor The background executor. - */ - MultiInstanceInvalidationClient(Context context, String name, - InvalidationTracker invalidationTracker, Executor executor) { - mAppContext = context.getApplicationContext(); - mName = name; - mInvalidationTracker = invalidationTracker; - mExecutor = executor; - mObserver = new InvalidationTracker.Observer(invalidationTracker.mTableNames) { - @Override - public void onInvalidated(@NonNull Set tables) { - if (mStopped.get()) { - return; - } - try { - final IMultiInstanceInvalidationService service = mService; - if (service != null) { - service.broadcastInvalidation(mClientId, tables.toArray(new String[0])); - } - } catch (RemoteException e) { - Log.w(Room.LOG_TAG, "Cannot broadcast invalidation", e); - } - } - - @Override - boolean isRemote() { - return true; - } - }; - Intent intent = new Intent(mAppContext, MultiInstanceInvalidationService.class); - mAppContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); - } - - void stop() { - if (mStopped.compareAndSet(false, true)) { - mExecutor.execute(mTearDownRunnable); - } - } -} diff --git a/app/src/main/java/androidx/room/MultiInstanceInvalidationService.java b/app/src/main/java/androidx/room/MultiInstanceInvalidationService.java deleted file mode 100644 index 2e98f120ff..0000000000 --- a/app/src/main/java/androidx/room/MultiInstanceInvalidationService.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; - -import java.util.HashMap; - -/** - * A {@link Service} for remote invalidation among multiple {@link InvalidationTracker} instances. - * This service runs in the main app process. All the instances of {@link InvalidationTracker} - * (potentially in other processes) has to connect to this service. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class MultiInstanceInvalidationService extends Service { - - // synthetic access - @SuppressWarnings("WeakerAccess") - int mMaxClientId = 0; - - // synthetic access - @SuppressWarnings("WeakerAccess") - final HashMap mClientNames = new HashMap<>(); - - // synthetic access - @SuppressWarnings("WeakerAccess") - final RemoteCallbackList mCallbackList = - new RemoteCallbackList() { - @Override - public void onCallbackDied(IMultiInstanceInvalidationCallback callback, - Object cookie) { - mClientNames.remove((int) cookie); - } - }; - - private final IMultiInstanceInvalidationService.Stub mBinder = - new IMultiInstanceInvalidationService.Stub() { - - // Assigns a client ID to the client. - @Override - public int registerCallback(IMultiInstanceInvalidationCallback callback, - String name) { - if (name == null) { - return 0; - } - synchronized (mCallbackList) { - int clientId = ++mMaxClientId; - // Use the client ID as the RemoteCallbackList cookie. - if (mCallbackList.register(callback, clientId)) { - mClientNames.put(clientId, name); - return clientId; - } else { - --mMaxClientId; - return 0; - } - } - } - - // Explicitly removes the client. - // The client can die without calling this. In that case, mCallbackList - // .onCallbackDied() can take care of removal. - @Override - public void unregisterCallback(IMultiInstanceInvalidationCallback callback, - int clientId) { - synchronized (mCallbackList) { - mCallbackList.unregister(callback); - mClientNames.remove(clientId); - } - } - - // Broadcasts table invalidation to other instances of the same database file. - // The broadcast is not sent to the caller itself. - @Override - public void broadcastInvalidation(int clientId, String[] tables) { - synchronized (mCallbackList) { - String name = mClientNames.get(clientId); - if (name == null) { - Log.w(Room.LOG_TAG, "Remote invalidation client ID not registered"); - return; - } - int count = mCallbackList.beginBroadcast(); - try { - for (int i = 0; i < count; i++) { - int targetClientId = (int) mCallbackList.getBroadcastCookie(i); - String targetName = mClientNames.get(targetClientId); - if (clientId == targetClientId // This is the caller itself. - || !name.equals(targetName)) { // Not the same file. - continue; - } - try { - IMultiInstanceInvalidationCallback callback = - mCallbackList.getBroadcastItem(i); - callback.onInvalidation(tables); - } catch (RemoteException e) { - Log.w(Room.LOG_TAG, "Error invoking a remote callback", e); - } - } - } finally { - mCallbackList.finishBroadcast(); - } - } - } - }; - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } -} diff --git a/app/src/main/java/androidx/room/Room.java b/app/src/main/java/androidx/room/Room.java deleted file mode 100644 index 2e4dedc7e4..0000000000 --- a/app/src/main/java/androidx/room/Room.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import android.content.Context; - -import androidx.annotation.NonNull; - -/** - * Utility class for Room. - */ -@SuppressWarnings("unused") -public class Room { - static final String LOG_TAG = "ROOM"; - /** - * The master table where room keeps its metadata information. - */ - public static final String MASTER_TABLE_NAME = RoomMasterTable.TABLE_NAME; - private static final String CURSOR_CONV_SUFFIX = "_CursorConverter"; - - /** - * Creates a RoomDatabase.Builder for a persistent database. Once a database is built, you - * should keep a reference to it and re-use it. - * - * @param context The context for the database. This is usually the Application context. - * @param klass The abstract class which is annotated with {@link Database} and extends - * {@link RoomDatabase}. - * @param name The name of the database file. - * @param The type of the database class. - * @return A {@code RoomDatabaseBuilder} which you can use to create the database. - */ - @SuppressWarnings("WeakerAccess") - @NonNull - public static RoomDatabase.Builder databaseBuilder( - @NonNull Context context, @NonNull Class klass, @NonNull String name) { - //noinspection ConstantConditions - if (name == null || name.trim().length() == 0) { - throw new IllegalArgumentException("Cannot build a database with null or empty name." - + " If you are trying to create an in memory database, use Room" - + ".inMemoryDatabaseBuilder"); - } - return new RoomDatabase.Builder<>(context, klass, name); - } - - /** - * Creates a RoomDatabase.Builder for an in memory database. Information stored in an in memory - * database disappears when the process is killed. - * Once a database is built, you should keep a reference to it and re-use it. - * - * @param context The context for the database. This is usually the Application context. - * @param klass The abstract class which is annotated with {@link Database} and extends - * {@link RoomDatabase}. - * @param The type of the database class. - * @return A {@code RoomDatabaseBuilder} which you can use to create the database. - */ - @NonNull - public static RoomDatabase.Builder inMemoryDatabaseBuilder( - @NonNull Context context, @NonNull Class klass) { - return new RoomDatabase.Builder<>(context, klass, null); - } - - @SuppressWarnings({"TypeParameterUnusedInFormals", "ClassNewInstance"}) - @NonNull - static T getGeneratedImplementation(Class klass, String suffix) { - final String fullPackage = klass.getPackage().getName(); - String name = klass.getCanonicalName(); - final String postPackageName = fullPackage.isEmpty() - ? name - : (name.substring(fullPackage.length() + 1)); - final String implName = postPackageName.replace('.', '_') + suffix; - //noinspection TryWithIdenticalCatches - try { - - @SuppressWarnings("unchecked") - final Class aClass = (Class) Class.forName( - fullPackage.isEmpty() ? implName : fullPackage + "." + implName); - return aClass.newInstance(); - } catch (ClassNotFoundException e) { - throw new RuntimeException("cannot find implementation for " - + klass.getCanonicalName() + ". " + implName + " does not exist"); - } catch (IllegalAccessException e) { - throw new RuntimeException("Cannot access the constructor" - + klass.getCanonicalName()); - } catch (InstantiationException e) { - throw new RuntimeException("Failed to create an instance of " - + klass.getCanonicalName()); - } - } - - /** @deprecated This type should not be instantiated as it contains only static methods. */ - @Deprecated - @SuppressWarnings("PrivateConstructorForUtilityClass") - public Room() { - } -} diff --git a/app/src/main/java/androidx/room/RoomDatabase.java b/app/src/main/java/androidx/room/RoomDatabase.java deleted file mode 100644 index 2e88a2891d..0000000000 --- a/app/src/main/java/androidx/room/RoomDatabase.java +++ /dev/null @@ -1,1065 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.content.Context; -import android.database.Cursor; -import android.os.Build; -import android.os.Looper; -import android.util.Log; - -import androidx.annotation.CallSuper; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.annotation.RestrictTo; -import androidx.annotation.WorkerThread; -import androidx.arch.core.executor.ArchTaskExecutor; -import androidx.room.migration.Migration; -import androidx.room.util.SneakyThrow; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.sqlite.db.SupportSQLiteOpenHelper; -import androidx.sqlite.db.SupportSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteStatement; -import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * Base class for all Room databases. All classes that are annotated with {@link Database} must - * extend this class. - *

- * RoomDatabase provides direct access to the underlying database implementation but you should - * prefer using {@link Dao} classes. - * - * @see Database - */ -public abstract class RoomDatabase { - private static final String DB_IMPL_SUFFIX = "_Impl"; - /** - * Unfortunately, we cannot read this value so we are only setting it to the SQLite default. - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public static final int MAX_BIND_PARAMETER_CNT = 999; - /** - * Set by the generated open helper. - * - * @deprecated Will be hidden in the next release. - */ - @Deprecated - protected volatile SupportSQLiteDatabase mDatabase; - private Executor mQueryExecutor; - private Executor mTransactionExecutor; - private SupportSQLiteOpenHelper mOpenHelper; - private final InvalidationTracker mInvalidationTracker; - private boolean mAllowMainThreadQueries; - boolean mWriteAheadLoggingEnabled; - - /** - * @deprecated Will be hidden in the next release. - */ - @Nullable - @Deprecated - protected List mCallbacks; - - private final ReentrantReadWriteLock mCloseLock = new ReentrantReadWriteLock(); - - /** - * {@link InvalidationTracker} uses this lock to prevent the database from closing while it is - * querying database updates. - *

- * The returned lock is reentrant and will allow multiple threads to acquire the lock - * simultaneously until {@link #close()} is invoked in which the lock becomes exclusive as - * a way to let the InvalidationTracker finish its work before closing the database. - * - * @return The lock for {@link #close()}. - */ - Lock getCloseLock() { - return mCloseLock.readLock(); - } - - /** - * This id is only set on threads that are used to dispatch coroutines within a suspending - * database transaction. - */ - private final ThreadLocal mSuspendingTransactionId = new ThreadLocal<>(); - - /** - * Gets the suspending transaction id of the current thread. - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - ThreadLocal getSuspendingTransactionId() { - return mSuspendingTransactionId; - } - - - private final Map mBackingFieldMap = new ConcurrentHashMap<>(); - - /** - * Gets the map for storing extension properties of Kotlin type. - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - Map getBackingFieldMap() { - return mBackingFieldMap; - } - - /** - * Creates a RoomDatabase. - *

- * You cannot create an instance of a database, instead, you should acquire it via - * {@link Room#databaseBuilder(Context, Class, String)} or - * {@link Room#inMemoryDatabaseBuilder(Context, Class)}. - */ - public RoomDatabase() { - mInvalidationTracker = createInvalidationTracker(); - } - - /** - * Called by {@link Room} when it is initialized. - * - * @param configuration The database configuration. - */ - @CallSuper - public void init(@NonNull DatabaseConfiguration configuration) { - mOpenHelper = createOpenHelper(configuration); - if (mOpenHelper instanceof SQLiteCopyOpenHelper) { - SQLiteCopyOpenHelper copyOpenHelper = (SQLiteCopyOpenHelper) mOpenHelper; - copyOpenHelper.setDatabaseConfiguration(configuration); - } - boolean wal = false; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - wal = configuration.journalMode == JournalMode.WRITE_AHEAD_LOGGING; - mOpenHelper.setWriteAheadLoggingEnabled(wal); - } - mCallbacks = configuration.callbacks; - mQueryExecutor = configuration.queryExecutor; - mTransactionExecutor = new TransactionExecutor(configuration.transactionExecutor); - mAllowMainThreadQueries = configuration.allowMainThreadQueries; - mWriteAheadLoggingEnabled = wal; - if (configuration.multiInstanceInvalidation) { - mInvalidationTracker.startMultiInstanceInvalidation(configuration.context, - configuration.name); - } - } - - /** - * Returns the SQLite open helper used by this database. - * - * @return The SQLite open helper used by this database. - */ - @NonNull - public SupportSQLiteOpenHelper getOpenHelper() { - return mOpenHelper; - } - - /** - * Creates the open helper to access the database. Generated class already implements this - * method. - * Note that this method is called when the RoomDatabase is initialized. - * - * @param config The configuration of the Room database. - * @return A new SupportSQLiteOpenHelper to be used while connecting to the database. - */ - @NonNull - protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config); - - /** - * Called when the RoomDatabase is created. - *

- * This is already implemented by the generated code. - * - * @return Creates a new InvalidationTracker. - */ - @NonNull - protected abstract InvalidationTracker createInvalidationTracker(); - - /** - * Deletes all rows from all the tables that are registered to this database as - * {@link Database#entities()}. - *

- * This does NOT reset the auto-increment value generated by {@link PrimaryKey#autoGenerate()}. - *

- * After deleting the rows, Room will set a WAL checkpoint and run VACUUM. This means that the - * data is completely erased. The space will be reclaimed by the system if the amount surpasses - * the threshold of database file size. - * - * @see Database File Format - */ - @WorkerThread - public abstract void clearAllTables(); - - /** - * Returns true if database connection is open and initialized. - * - * @return true if the database connection is open, false otherwise. - */ - public boolean isOpen() { - final SupportSQLiteDatabase db = mDatabase; - return db != null && db.isOpen(); - } - - /** - * Closes the database if it is already open. - */ - public void close() { - if (isOpen()) { - final Lock closeLock = mCloseLock.writeLock(); - try { - closeLock.lock(); - mInvalidationTracker.stopMultiInstanceInvalidation(); - mOpenHelper.close(); - } finally { - closeLock.unlock(); - } - } - } - - /** - * Asserts that we are not on the main thread. - * - * @hide - */ - @SuppressWarnings("WeakerAccess") - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - // used in generated code - public void assertNotMainThread() { - if (mAllowMainThreadQueries) { - return; - } - if (isMainThread()) { - throw new IllegalStateException("Cannot access database on the main thread since" - + " it may potentially lock the UI for a long period of time."); - } - } - - /** - * Asserts that we are not on a suspending transaction. - * - * @hide - */ - @SuppressWarnings("WeakerAccess") - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - // used in generated code - public void assertNotSuspendingTransaction() { - if (!inTransaction() && mSuspendingTransactionId.get() != null) { - throw new IllegalStateException("Cannot access database on a different coroutine" - + " context inherited from a suspending transaction."); - } - } - - // Below, there are wrapper methods for SupportSQLiteDatabase. This helps us track which - // methods we are using and also helps unit tests to mock this class without mocking - // all SQLite database methods. - - /** - * Convenience method to query the database with arguments. - * - * @param query The sql query - * @param args The bind arguments for the placeholders in the query - * @return A Cursor obtained by running the given query in the Room database. - */ - public Cursor query(String query, @Nullable Object[] args) { - return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args)); - } - - /** - * Wrapper for {@link SupportSQLiteDatabase#query(SupportSQLiteQuery)}. - * - * @param query The Query which includes the SQL and a bind callback for bind arguments. - * @return Result of the query. - */ - public Cursor query(SupportSQLiteQuery query) { - assertNotMainThread(); - assertNotSuspendingTransaction(); - return mOpenHelper.getWritableDatabase().query(query); - } - - /** - * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}. - * - * @param sql The query to compile. - * @return The compiled query. - */ - public SupportSQLiteStatement compileStatement(@NonNull String sql) { - assertNotMainThread(); - assertNotSuspendingTransaction(); - return mOpenHelper.getWritableDatabase().compileStatement(sql); - } - - /** - * Wrapper for {@link SupportSQLiteDatabase#beginTransaction()}. - * - * @deprecated Use {@link #runInTransaction(Runnable)} - */ - @Deprecated - public void beginTransaction() { - assertNotMainThread(); - SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase(); - mInvalidationTracker.syncTriggers(database); - database.beginTransaction(); - } - - /** - * Wrapper for {@link SupportSQLiteDatabase#endTransaction()}. - * - * @deprecated Use {@link #runInTransaction(Runnable)} - */ - @Deprecated - public void endTransaction() { - mOpenHelper.getWritableDatabase().endTransaction(); - if (!inTransaction()) { - // enqueue refresh only if we are NOT in a transaction. Otherwise, wait for the last - // endTransaction call to do it. - mInvalidationTracker.refreshVersionsAsync(); - } - } - - /** - * @return The Executor in use by this database for async queries. - */ - @NonNull - public Executor getQueryExecutor() { - return mQueryExecutor; - } - - /** - * @return The Executor in use by this database for async transactions. - */ - @NonNull - public Executor getTransactionExecutor() { - return mTransactionExecutor; - } - - /** - * Wrapper for {@link SupportSQLiteDatabase#setTransactionSuccessful()}. - * - * @deprecated Use {@link #runInTransaction(Runnable)} - */ - @Deprecated - public void setTransactionSuccessful() { - mOpenHelper.getWritableDatabase().setTransactionSuccessful(); - } - - /** - * Executes the specified {@link Runnable} in a database transaction. The transaction will be - * marked as successful unless an exception is thrown in the {@link Runnable}. - *

- * Room will only perform at most one transaction at a time. - * - * @param body The piece of code to execute. - */ - @SuppressWarnings("deprecation") - public void runInTransaction(@NonNull Runnable body) { - beginTransaction(); - try { - body.run(); - setTransactionSuccessful(); - } finally { - endTransaction(); - } - } - - /** - * Executes the specified {@link Callable} in a database transaction. The transaction will be - * marked as successful unless an exception is thrown in the {@link Callable}. - *

- * Room will only perform at most one transaction at a time. - * - * @param body The piece of code to execute. - * @param The type of the return value. - * @return The value returned from the {@link Callable}. - */ - @SuppressWarnings("deprecation") - public V runInTransaction(@NonNull Callable body) { - beginTransaction(); - try { - V result = body.call(); - setTransactionSuccessful(); - return result; - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - SneakyThrow.reThrow(e); - return null; // Unreachable code, but compiler doesn't know it. - } finally { - endTransaction(); - } - } - - /** - * Called by the generated code when database is open. - *

- * You should never call this method manually. - * - * @param db The database instance. - */ - protected void internalInitInvalidationTracker(@NonNull SupportSQLiteDatabase db) { - mInvalidationTracker.internalInit(db); - } - - /** - * Returns the invalidation tracker for this database. - *

- * You can use the invalidation tracker to get notified when certain tables in the database - * are modified. - * - * @return The invalidation tracker for the database. - */ - @NonNull - public InvalidationTracker getInvalidationTracker() { - return mInvalidationTracker; - } - - /** - * Returns true if current thread is in a transaction. - * - * @return True if there is an active transaction in current thread, false otherwise. - * @see SupportSQLiteDatabase#inTransaction() - */ - @SuppressWarnings("WeakerAccess") - public boolean inTransaction() { - return mOpenHelper.getWritableDatabase().inTransaction(); - } - - /** - * Journal modes for SQLite database. - * - * @see RoomDatabase.Builder#setJournalMode(JournalMode) - */ - public enum JournalMode { - - /** - * Let Room choose the journal mode. This is the default value when no explicit value is - * specified. - *

- * The actual value will be {@link #TRUNCATE} when the device runs API Level lower than 16 - * or it is a low-RAM device. Otherwise, {@link #WRITE_AHEAD_LOGGING} will be used. - */ - AUTOMATIC, - - /** - * Truncate journal mode. - */ - TRUNCATE, - - /** - * Write-Ahead Logging mode. - */ - @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) - WRITE_AHEAD_LOGGING; - - /** - * Resolves {@link #AUTOMATIC} to either {@link #TRUNCATE} or - * {@link #WRITE_AHEAD_LOGGING}. - */ - @SuppressLint("NewApi") - JournalMode resolve(Context context) { - if (this != AUTOMATIC) { - return this; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - ActivityManager manager = (ActivityManager) - context.getSystemService(Context.ACTIVITY_SERVICE); - if (manager != null && !isLowRamDevice(manager)) { - return WRITE_AHEAD_LOGGING; - } - } - return TRUNCATE; - } - - private static boolean isLowRamDevice(@NonNull ActivityManager activityManager) { - if (Build.VERSION.SDK_INT >= 19) { - return activityManager.isLowRamDevice(); - } - return false; - } - } - - /** - * Builder for RoomDatabase. - * - * @param The type of the abstract database class. - */ - public static class Builder { - private final Class mDatabaseClass; - private final String mName; - private final Context mContext; - private ArrayList mCallbacks; - - /** The Executor used to run database queries. This should be background-threaded. */ - private Executor mQueryExecutor; - /** The Executor used to run database transactions. This should be background-threaded. */ - private Executor mTransactionExecutor; - private SupportSQLiteOpenHelper.Factory mFactory; - private boolean mAllowMainThreadQueries; - private JournalMode mJournalMode; - private boolean mMultiInstanceInvalidation; - private boolean mRequireMigration; - private boolean mAllowDestructiveMigrationOnDowngrade; - /** - * Migrations, mapped by from-to pairs. - */ - private final MigrationContainer mMigrationContainer; - private Set mMigrationsNotRequiredFrom; - /** - * Keeps track of {@link Migration#startVersion}s and {@link Migration#endVersion}s added in - * {@link #addMigrations(Migration...)} for later validation that makes those versions don't - * match any versions passed to {@link #fallbackToDestructiveMigrationFrom(int...)}. - */ - private Set mMigrationStartAndEndVersions; - - private String mCopyFromAssetPath; - private File mCopyFromFile; - - Builder(@NonNull Context context, @NonNull Class klass, @Nullable String name) { - mContext = context; - mDatabaseClass = klass; - mName = name; - mJournalMode = JournalMode.AUTOMATIC; - mRequireMigration = true; - mMigrationContainer = new MigrationContainer(); - } - - /** - * Configures Room to create and open the database using a pre-packaged database located in - * the application 'assets/' folder. - *

- * Room does not open the pre-packaged database, instead it copies it into the internal - * app database folder and then opens it. The pre-packaged database file must be located in - * the "assets/" folder of your application. For example, the path for a file located in - * "assets/databases/products.db" would be "databases/products.db". - *

- * The pre-packaged database schema will be validated. It might be best to create your - * pre-packaged database schema utilizing the exported schema files generated when - * {@link Database#exportSchema()} is enabled. - *

- * This method is not supported for an in memory database {@link Builder}. - * - * @param databaseFilePath The file path within the 'assets/' directory of where the - * database file is located. - * - * @return This {@link Builder} instance. - */ - @NonNull - public Builder createFromAsset(@NonNull String databaseFilePath) { - mCopyFromAssetPath = databaseFilePath; - return this; - } - - /** - * Configures Room to create and open the database using a pre-packaged database file. - *

- * Room does not open the pre-packaged database, instead it copies it into the internal - * app database folder and then opens it. The given file must be accessible and the right - * permissions must be granted for Room to copy the file. - *

- * The pre-packaged database schema will be validated. It might be best to create your - * pre-packaged database schema utilizing the exported schema files generated when - * {@link Database#exportSchema()} is enabled. - *

- * This method is not supported for an in memory database {@link Builder}. - * - * @param databaseFile The database file. - * - * @return This {@link Builder} instance. - */ - @NonNull - public Builder createFromFile(@NonNull File databaseFile) { - mCopyFromFile = databaseFile; - return this; - } - - /** - * Sets the database factory. If not set, it defaults to - * {@link FrameworkSQLiteOpenHelperFactory}. - * - * @param factory The factory to use to access the database. - * @return This {@link Builder} instance. - */ - @NonNull - public Builder openHelperFactory(@Nullable SupportSQLiteOpenHelper.Factory factory) { - mFactory = factory; - return this; - } - - /** - * Adds a migration to the builder. - *

- * Each Migration has a start and end versions and Room runs these migrations to bring the - * database to the latest version. - *

- * If a migration item is missing between current version and the latest version, Room - * will clear the database and recreate so even if you have no changes between 2 versions, - * you should still provide a Migration object to the builder. - *

- * A migration can handle more than 1 version (e.g. if you have a faster path to choose when - * going version 3 to 5 without going to version 4). If Room opens a database at version - * 3 and latest version is >= 5, Room will use the migration object that can migrate from - * 3 to 5 instead of 3 to 4 and 4 to 5. - * - * @param migrations The migration object that can modify the database and to the necessary - * changes. - * @return This {@link Builder} instance. - */ - @NonNull - public Builder addMigrations(@NonNull Migration... migrations) { - if (mMigrationStartAndEndVersions == null) { - mMigrationStartAndEndVersions = new HashSet<>(); - } - for (Migration migration : migrations) { - mMigrationStartAndEndVersions.add(migration.startVersion); - mMigrationStartAndEndVersions.add(migration.endVersion); - } - - mMigrationContainer.addMigrations(migrations); - return this; - } - - /** - * Disables the main thread query check for Room. - *

- * Room ensures that Database is never accessed on the main thread because it may lock the - * main thread and trigger an ANR. If you need to access the database from the main thread, - * you should always use async alternatives or manually move the call to a background - * thread. - *

- * You may want to turn this check off for testing. - * - * @return This {@link Builder} instance. - */ - @NonNull - public Builder allowMainThreadQueries() { - mAllowMainThreadQueries = true; - return this; - } - - /** - * Sets the journal mode for this database. - * - *

- * This value is ignored if the builder is initialized with - * {@link Room#inMemoryDatabaseBuilder(Context, Class)}. - *

- * The journal mode should be consistent across multiple instances of - * {@link RoomDatabase} for a single SQLite database file. - *

- * The default value is {@link JournalMode#AUTOMATIC}. - * - * @param journalMode The journal mode. - * @return This {@link Builder} instance. - */ - @NonNull - public Builder setJournalMode(@NonNull JournalMode journalMode) { - mJournalMode = journalMode; - return this; - } - - /** - * Sets the {@link Executor} that will be used to execute all non-blocking asynchronous - * queries and tasks, including {@code LiveData} invalidation, {@code Flowable} scheduling - * and {@code ListenableFuture} tasks. - *

- * When both the query executor and transaction executor are unset, then a default - * {@code Executor} will be used. The default {@code Executor} allocates and shares threads - * amongst Architecture Components libraries. If the query executor is unset but a - * transaction executor was set, then the same {@code Executor} will be used for queries. - *

- * For best performance the given {@code Executor} should be bounded (max number of threads - * is limited). - *

- * The input {@code Executor} cannot run tasks on the UI thread. - ** - * @return This {@link Builder} instance. - * - * @see #setTransactionExecutor(Executor) - */ - @NonNull - public Builder setQueryExecutor(@NonNull Executor executor) { - mQueryExecutor = executor; - return this; - } - - /** - * Sets the {@link Executor} that will be used to execute all non-blocking asynchronous - * transaction queries and tasks, including {@code LiveData} invalidation, {@code Flowable} - * scheduling and {@code ListenableFuture} tasks. - *

- * When both the transaction executor and query executor are unset, then a default - * {@code Executor} will be used. The default {@code Executor} allocates and shares threads - * amongst Architecture Components libraries. If the transaction executor is unset but a - * query executor was set, then the same {@code Executor} will be used for transactions. - *

- * If the given {@code Executor} is shared then it should be unbounded to avoid the - * possibility of a deadlock. Room will not use more than one thread at a time from this - * executor since only one transaction at a time can be executed, other transactions will - * be queued on a first come, first serve order. - *

- * The input {@code Executor} cannot run tasks on the UI thread. - * - * @return This {@link Builder} instance. - * - * @see #setQueryExecutor(Executor) - */ - @NonNull - public Builder setTransactionExecutor(@NonNull Executor executor) { - mTransactionExecutor = executor; - return this; - } - - /** - * Sets whether table invalidation in this instance of {@link RoomDatabase} should be - * broadcast and synchronized with other instances of the same {@link RoomDatabase}, - * including those in a separate process. In order to enable multi-instance invalidation, - * this has to be turned on both ends. - *

- * This is not enabled by default. - *

- * This does not work for in-memory databases. This does not work between database instances - * targeting different database files. - * - * @return This {@link Builder} instance. - */ - @NonNull - public Builder enableMultiInstanceInvalidation() { - mMultiInstanceInvalidation = mName != null; - return this; - } - - /** - * Allows Room to destructively recreate database tables if {@link Migration}s that would - * migrate old database schemas to the latest schema version are not found. - *

- * When the database version on the device does not match the latest schema version, Room - * runs necessary {@link Migration}s on the database. - *

- * If it cannot find the set of {@link Migration}s that will bring the database to the - * current version, it will throw an {@link IllegalStateException}. - *

- * You can call this method to change this behavior to re-create the database instead of - * crashing. - *

- * If the database was create from an asset or a file then Room will try to use the same - * file to re-create the database, otherwise this will delete all of the data in the - * database tables managed by Room. - *

- * To let Room fallback to destructive migration only during a schema downgrade then use - * {@link #fallbackToDestructiveMigrationOnDowngrade()}. - * - * @return This {@link Builder} instance. - * - * @see #fallbackToDestructiveMigrationOnDowngrade() - */ - @NonNull - public Builder fallbackToDestructiveMigration() { - mRequireMigration = false; - mAllowDestructiveMigrationOnDowngrade = true; - return this; - } - - /** - * Allows Room to destructively recreate database tables if {@link Migration}s are not - * available when downgrading to old schema versions. - * - * @return This {@link Builder} instance. - * - * @see Builder#fallbackToDestructiveMigration() - */ - @NonNull - public Builder fallbackToDestructiveMigrationOnDowngrade() { - mRequireMigration = true; - mAllowDestructiveMigrationOnDowngrade = true; - return this; - } - - /** - * Informs Room that it is allowed to destructively recreate database tables from specific - * starting schema versions. - *

- * This functionality is the same as that provided by - * {@link #fallbackToDestructiveMigration()}, except that this method allows the - * specification of a set of schema versions for which destructive recreation is allowed. - *

- * Using this method is preferable to {@link #fallbackToDestructiveMigration()} if you want - * to allow destructive migrations from some schema versions while still taking advantage - * of exceptions being thrown due to unintentionally missing migrations. - *

- * Note: No versions passed to this method may also exist as either starting or ending - * versions in the {@link Migration}s provided to {@link #addMigrations(Migration...)}. If a - * version passed to this method is found as a starting or ending version in a Migration, an - * exception will be thrown. - * - * @param startVersions The set of schema versions from which Room should use a destructive - * migration. - * @return This {@link Builder} instance. - */ - @NonNull - public Builder fallbackToDestructiveMigrationFrom(int... startVersions) { - if (mMigrationsNotRequiredFrom == null) { - mMigrationsNotRequiredFrom = new HashSet<>(startVersions.length); - } - for (int startVersion : startVersions) { - mMigrationsNotRequiredFrom.add(startVersion); - } - return this; - } - - /** - * Adds a {@link Callback} to this database. - * - * @param callback The callback. - * @return This {@link Builder} instance. - */ - @NonNull - public Builder addCallback(@NonNull Callback callback) { - if (mCallbacks == null) { - mCallbacks = new ArrayList<>(); - } - mCallbacks.add(callback); - return this; - } - - /** - * Creates the databases and initializes it. - *

- * By default, all RoomDatabases use in memory storage for TEMP tables and enables recursive - * triggers. - * - * @return A new database instance. - */ - @SuppressLint("RestrictedApi") - @NonNull - public T build() { - //noinspection ConstantConditions - if (mContext == null) { - throw new IllegalArgumentException("Cannot provide null context for the database."); - } - //noinspection ConstantConditions - if (mDatabaseClass == null) { - throw new IllegalArgumentException("Must provide an abstract class that" - + " extends RoomDatabase"); - } - if (mQueryExecutor == null && mTransactionExecutor == null) { - mQueryExecutor = mTransactionExecutor = ArchTaskExecutor.getIOThreadExecutor(); - } else if (mQueryExecutor != null && mTransactionExecutor == null) { - mTransactionExecutor = mQueryExecutor; - } else if (mQueryExecutor == null && mTransactionExecutor != null) { - mQueryExecutor = mTransactionExecutor; - } - - if (mMigrationStartAndEndVersions != null && mMigrationsNotRequiredFrom != null) { - for (Integer version : mMigrationStartAndEndVersions) { - if (mMigrationsNotRequiredFrom.contains(version)) { - throw new IllegalArgumentException( - "Inconsistency detected. A Migration was supplied to " - + "addMigration(Migration... migrations) that has a start " - + "or end version equal to a start version supplied to " - + "fallbackToDestructiveMigrationFrom(int... " - + "startVersions). Start version: " - + version); - } - } - } - - if (mFactory == null) { - mFactory = new FrameworkSQLiteOpenHelperFactory(); - } - - if (mCopyFromAssetPath != null || mCopyFromFile != null) { - if (mName == null) { - throw new IllegalArgumentException("Cannot create from asset or file for an " - + "in-memory database."); - } - if (mCopyFromAssetPath != null && mCopyFromFile != null) { - throw new IllegalArgumentException("Both createFromAsset() and " - + "createFromFile() was called on this Builder but the database can " - + "only be created using one of the two configurations."); - } - mFactory = new SQLiteCopyOpenHelperFactory(mCopyFromAssetPath, mCopyFromFile, - mFactory); - } - DatabaseConfiguration configuration = - new DatabaseConfiguration( - mContext, - mName, - mFactory, - mMigrationContainer, - mCallbacks, - mAllowMainThreadQueries, - mJournalMode.resolve(mContext), - mQueryExecutor, - mTransactionExecutor, - mMultiInstanceInvalidation, - mRequireMigration, - mAllowDestructiveMigrationOnDowngrade, - mMigrationsNotRequiredFrom, - mCopyFromAssetPath, - mCopyFromFile); - T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX); - db.init(configuration); - return db; - } - } - - /** - * A container to hold migrations. It also allows querying its contents to find migrations - * between two versions. - */ - public static class MigrationContainer { - private HashMap> mMigrations = new HashMap<>(); - - /** - * Adds the given migrations to the list of available migrations. If 2 migrations have the - * same start-end versions, the latter migration overrides the previous one. - * - * @param migrations List of available migrations. - */ - public void addMigrations(@NonNull Migration... migrations) { - for (Migration migration : migrations) { - addMigration(migration); - } - } - - private void addMigration(Migration migration) { - final int start = migration.startVersion; - final int end = migration.endVersion; - TreeMap targetMap = mMigrations.get(start); - if (targetMap == null) { - targetMap = new TreeMap<>(); - mMigrations.put(start, targetMap); - } - Migration existing = targetMap.get(end); - if (existing != null) { - Log.w(Room.LOG_TAG, "Overriding migration " + existing + " with " + migration); - } - targetMap.put(end, migration); - } - - /** - * Finds the list of migrations that should be run to move from {@code start} version to - * {@code end} version. - * - * @param start The current database version - * @param end The target database version - * @return An ordered list of {@link Migration} objects that should be run to migrate - * between the given versions. If a migration path cannot be found, returns {@code null}. - */ - @SuppressWarnings("WeakerAccess") - @Nullable - public List findMigrationPath(int start, int end) { - if (start == end) { - return Collections.emptyList(); - } - boolean migrateUp = end > start; - List result = new ArrayList<>(); - return findUpMigrationPath(result, migrateUp, start, end); - } - - private List findUpMigrationPath(List result, boolean upgrade, - int start, int end) { - while (upgrade ? start < end : start > end) { - TreeMap targetNodes = mMigrations.get(start); - if (targetNodes == null) { - return null; - } - // keys are ordered so we can start searching from one end of them. - Set keySet; - if (upgrade) { - keySet = targetNodes.descendingKeySet(); - } else { - keySet = targetNodes.keySet(); - } - boolean found = false; - for (int targetVersion : keySet) { - final boolean shouldAddToPath; - if (upgrade) { - shouldAddToPath = targetVersion <= end && targetVersion > start; - } else { - shouldAddToPath = targetVersion >= end && targetVersion < start; - } - if (shouldAddToPath) { - result.add(targetNodes.get(targetVersion)); - start = targetVersion; - found = true; - break; - } - } - if (!found) { - return null; - } - } - return result; - } - } - - /** Returns true if the calling thread is the main thread. */ - private static boolean isMainThread() { - return Looper.getMainLooper().getThread() == Thread.currentThread(); - } - - /** - * Callback for {@link RoomDatabase}. - */ - public abstract static class Callback { - - /** - * Called when the database is created for the first time. This is called after all the - * tables are created. - * - * @param db The database. - */ - public void onCreate(@NonNull SupportSQLiteDatabase db) { - } - - /** - * Called when the database has been opened. - * - * @param db The database. - */ - public void onOpen(@NonNull SupportSQLiteDatabase db) { - } - - /** - * Called after the database was destructively migrated - * - * @param db The database. - */ - public void onDestructiveMigration(@NonNull SupportSQLiteDatabase db){ - } - } -} diff --git a/app/src/main/java/androidx/room/RoomOpenHelper.java b/app/src/main/java/androidx/room/RoomOpenHelper.java deleted file mode 100644 index 3dbe7ddeac..0000000000 --- a/app/src/main/java/androidx/room/RoomOpenHelper.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import android.database.Cursor; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.room.migration.Migration; -import androidx.sqlite.db.SimpleSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.sqlite.db.SupportSQLiteOpenHelper; - -import java.util.List; - -/** - * An open helper that holds a reference to the configuration until the database is opened. - * - * @hide - */ -@SuppressWarnings("unused") -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback { - @Nullable - private DatabaseConfiguration mConfiguration; - @NonNull - private final Delegate mDelegate; - @NonNull - private final String mIdentityHash; - /** - * Room v1 had a bug where the hash was not consistent if fields are reordered. - * The new has fixes it but we still need to accept the legacy hash. - */ - @NonNull // b/64290754 - private final String mLegacyHash; - - public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate, - @NonNull String identityHash, @NonNull String legacyHash) { - super(delegate.version); - mConfiguration = configuration; - mDelegate = delegate; - mIdentityHash = identityHash; - mLegacyHash = legacyHash; - } - - public RoomOpenHelper(@NonNull DatabaseConfiguration configuration, @NonNull Delegate delegate, - @NonNull String legacyHash) { - this(configuration, delegate, "", legacyHash); - } - - @Override - public void onConfigure(SupportSQLiteDatabase db) { - super.onConfigure(db); - } - - @Override - public void onCreate(SupportSQLiteDatabase db) { - boolean isEmptyDatabase = hasEmptySchema(db); - mDelegate.createAllTables(db); - if (!isEmptyDatabase) { - // A 0 version pre-populated database goes through the create path because the - // framework's SQLiteOpenHelper thinks the database was just created from scratch. If we - // find the database not to be empty, then it is a pre-populated, we must validate it to - // see if its suitable for usage. - ValidationResult result = mDelegate.onValidateSchema(db); - if (!result.isValid) { - throw new IllegalStateException("Pre-packaged database has an invalid schema: " - + result.expectedFoundMsg); - } - } - updateIdentity(db); - mDelegate.onCreate(db); - } - - @Override - public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { - boolean migrated = false; - if (mConfiguration != null) { - List migrations = mConfiguration.migrationContainer.findMigrationPath( - oldVersion, newVersion); - if (migrations != null) { - mDelegate.onPreMigrate(db); - for (Migration migration : migrations) { - migration.migrate(db); - } - ValidationResult result = mDelegate.onValidateSchema(db); - if (!result.isValid) { - throw new IllegalStateException("Migration didn't properly handle: " - + result.expectedFoundMsg); - } - mDelegate.onPostMigrate(db); - updateIdentity(db); - migrated = true; - } - } - if (!migrated) { - if (mConfiguration != null - && !mConfiguration.isMigrationRequired(oldVersion, newVersion)) { - mDelegate.dropAllTables(db); - mDelegate.createAllTables(db); - } else { - throw new IllegalStateException("A migration from " + oldVersion + " to " - + newVersion + " was required but not found. Please provide the " - + "necessary Migration path via " - + "RoomDatabase.Builder.addMigration(Migration ...) or allow for " - + "destructive migrations via one of the " - + "RoomDatabase.Builder.fallbackToDestructiveMigration* methods."); - } - } - } - - @Override - public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) { - onUpgrade(db, oldVersion, newVersion); - } - - @Override - public void onOpen(SupportSQLiteDatabase db) { - super.onOpen(db); - checkIdentity(db); - mDelegate.onOpen(db); - // there might be too many configurations etc, just clear it. - mConfiguration = null; - } - - private void checkIdentity(SupportSQLiteDatabase db) { - if (hasRoomMasterTable(db)) { - String identityHash = null; - Cursor cursor = db.query(new SimpleSQLiteQuery(RoomMasterTable.READ_QUERY)); - //noinspection TryFinallyCanBeTryWithResources - try { - if (cursor.moveToFirst()) { - identityHash = cursor.getString(0); - } - } finally { - cursor.close(); - } - if (!mIdentityHash.equals(identityHash) && !mLegacyHash.equals(identityHash)) { - throw new IllegalStateException("Room cannot verify the data integrity. Looks like" - + " you've changed schema but forgot to update the version number. You can" - + " simply fix this by increasing the version number."); - } - } else { - // No room_master_table, this might an a pre-populated DB, we must validate to see if - // its suitable for usage. - ValidationResult result = mDelegate.onValidateSchema(db); - if (!result.isValid) { - throw new IllegalStateException("Pre-packaged database has an invalid schema: " - + result.expectedFoundMsg); - } - mDelegate.onPostMigrate(db); - updateIdentity(db); - } - } - - private void updateIdentity(SupportSQLiteDatabase db) { - createMasterTableIfNotExists(db); - db.execSQL(RoomMasterTable.createInsertQuery(mIdentityHash)); - } - - private void createMasterTableIfNotExists(SupportSQLiteDatabase db) { - db.execSQL(RoomMasterTable.CREATE_QUERY); - } - - private static boolean hasRoomMasterTable(SupportSQLiteDatabase db) { - Cursor cursor = db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name='" - + RoomMasterTable.TABLE_NAME + "'"); - //noinspection TryFinallyCanBeTryWithResources - try { - return cursor.moveToFirst() && cursor.getInt(0) != 0; - } finally { - cursor.close(); - } - } - - private static boolean hasEmptySchema(SupportSQLiteDatabase db) { - Cursor cursor = db.query( - "SELECT count(*) FROM sqlite_master WHERE name != 'android_metadata'"); - //noinspection TryFinallyCanBeTryWithResources - try { - return cursor.moveToFirst() && cursor.getInt(0) == 0; - } finally { - cursor.close(); - } - } - - /** - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public abstract static class Delegate { - public final int version; - - public Delegate(int version) { - this.version = version; - } - - protected abstract void dropAllTables(SupportSQLiteDatabase database); - - protected abstract void createAllTables(SupportSQLiteDatabase database); - - protected abstract void onOpen(SupportSQLiteDatabase database); - - protected abstract void onCreate(SupportSQLiteDatabase database); - - /** - * Called after a migration run to validate database integrity. - * - * @param db The SQLite database. - * - * @deprecated Use {@link #onValidateSchema(SupportSQLiteDatabase)} - */ - @Deprecated - protected void validateMigration(SupportSQLiteDatabase db) { - throw new UnsupportedOperationException("validateMigration is deprecated"); - } - - /** - * Called after a migration run or pre-package database copy to validate database integrity. - * - * @param db The SQLite database. - */ - @SuppressWarnings("deprecation") - @NonNull - protected ValidationResult onValidateSchema(@NonNull SupportSQLiteDatabase db) { - validateMigration(db); - return new ValidationResult(true, null); - } - - /** - * Called before migrations execute to perform preliminary work. - * @param database The SQLite database. - */ - protected void onPreMigrate(SupportSQLiteDatabase database) { - - } - - /** - * Called after migrations execute to perform additional work. - * @param database The SQLite database. - */ - protected void onPostMigrate(SupportSQLiteDatabase database) { - - } - } - - /** - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public static class ValidationResult { - - public final boolean isValid; - @Nullable - public final String expectedFoundMsg; - - public ValidationResult(boolean isValid, @Nullable String expectedFoundMsg) { - this.isValid = isValid; - this.expectedFoundMsg = expectedFoundMsg; - } - } -} diff --git a/app/src/main/java/androidx/room/RoomSQLiteQuery.java b/app/src/main/java/androidx/room/RoomSQLiteQuery.java deleted file mode 100644 index 689352c032..0000000000 --- a/app/src/main/java/androidx/room/RoomSQLiteQuery.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import androidx.annotation.IntDef; -import androidx.annotation.RestrictTo; -import androidx.annotation.VisibleForTesting; -import androidx.sqlite.db.SupportSQLiteProgram; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; -import java.util.Iterator; -import java.util.Map; -import java.util.TreeMap; - -/** - * This class is used as an intermediate place to keep binding arguments so that we can run - * Cursor queries with correct types rather than passing everything as a string. - *

- * Because it is relatively a big object, they are pooled and must be released after each use. - * - * @hide - */ -@SuppressWarnings("unused") -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram { - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - // Maximum number of queries we'll keep cached. - static final int POOL_LIMIT = 15; - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - // Once we hit POOL_LIMIT, we'll bring the pool size back to the desired number. We always - // clear the bigger queries (# of arguments). - static final int DESIRED_POOL_SIZE = 10; - private volatile String mQuery; - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - final long[] mLongBindings; - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - final double[] mDoubleBindings; - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - final String[] mStringBindings; - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - final byte[][] mBlobBindings; - - @Binding - private final int[] mBindingTypes; - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - final int mCapacity; - // number of arguments in the query - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - int mArgCount; - - - @SuppressWarnings("WeakerAccess") - @VisibleForTesting - static final TreeMap sQueryPool = new TreeMap<>(); - - /** - * Copies the given SupportSQLiteQuery and converts it into RoomSQLiteQuery. - * - * @param supportSQLiteQuery The query to copy from - * @return A new query copied from the provided one. - */ - public static RoomSQLiteQuery copyFrom(SupportSQLiteQuery supportSQLiteQuery) { - final RoomSQLiteQuery query = RoomSQLiteQuery.acquire( - supportSQLiteQuery.getSql(), - supportSQLiteQuery.getArgCount()); - supportSQLiteQuery.bindTo(new SupportSQLiteProgram() { - @Override - public void bindNull(int index) { - query.bindNull(index); - } - - @Override - public void bindLong(int index, long value) { - query.bindLong(index, value); - } - - @Override - public void bindDouble(int index, double value) { - query.bindDouble(index, value); - } - - @Override - public void bindString(int index, String value) { - query.bindString(index, value); - } - - @Override - public void bindBlob(int index, byte[] value) { - query.bindBlob(index, value); - } - - @Override - public void clearBindings() { - query.clearBindings(); - } - - @Override - public void close() { - // ignored. - } - }); - return query; - } - - /** - * Returns a new RoomSQLiteQuery that can accept the given number of arguments and holds the - * given query. - * - * @param query The query to prepare - * @param argumentCount The number of query arguments - * @return A RoomSQLiteQuery that holds the given query and has space for the given number of - * arguments. - */ - @SuppressWarnings("WeakerAccess") - public static RoomSQLiteQuery acquire(String query, int argumentCount) { - synchronized (sQueryPool) { - final Map.Entry entry = - sQueryPool.ceilingEntry(argumentCount); - if (entry != null) { - sQueryPool.remove(entry.getKey()); - final RoomSQLiteQuery sqliteQuery = entry.getValue(); - sqliteQuery.init(query, argumentCount); - return sqliteQuery; - } - } - RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount); - sqLiteQuery.init(query, argumentCount); - return sqLiteQuery; - } - - private RoomSQLiteQuery(int capacity) { - mCapacity = capacity; - // because, 1 based indices... we don't want to offsets everything with 1 all the time. - int limit = capacity + 1; - //noinspection WrongConstant - mBindingTypes = new int[limit]; - mLongBindings = new long[limit]; - mDoubleBindings = new double[limit]; - mStringBindings = new String[limit]; - mBlobBindings = new byte[limit][]; - } - - @SuppressWarnings("WeakerAccess") - void init(String query, int argCount) { - mQuery = query; - mArgCount = argCount; - } - - /** - * Releases the query back to the pool. - *

- * After released, the statement might be returned when {@link #acquire(String, int)} is called - * so you should never re-use it after releasing. - */ - @SuppressWarnings("WeakerAccess") - public void release() { - synchronized (sQueryPool) { - sQueryPool.put(mCapacity, this); - prunePoolLocked(); - } - } - - private static void prunePoolLocked() { - if (sQueryPool.size() > POOL_LIMIT) { - int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE; - final Iterator iterator = sQueryPool.descendingKeySet().iterator(); - while (toBeRemoved-- > 0) { - iterator.next(); - iterator.remove(); - } - } - } - - @Override - public String getSql() { - return mQuery; - } - - @Override - public int getArgCount() { - return mArgCount; - } - - @Override - public void bindTo(SupportSQLiteProgram program) { - for (int index = 1; index <= mArgCount; index++) { - switch (mBindingTypes[index]) { - case NULL: - program.bindNull(index); - break; - case LONG: - program.bindLong(index, mLongBindings[index]); - break; - case DOUBLE: - program.bindDouble(index, mDoubleBindings[index]); - break; - case STRING: - program.bindString(index, mStringBindings[index]); - break; - case BLOB: - program.bindBlob(index, mBlobBindings[index]); - break; - } - } - } - - @Override - public void bindNull(int index) { - mBindingTypes[index] = NULL; - } - - @Override - public void bindLong(int index, long value) { - mBindingTypes[index] = LONG; - mLongBindings[index] = value; - } - - @Override - public void bindDouble(int index, double value) { - mBindingTypes[index] = DOUBLE; - mDoubleBindings[index] = value; - } - - @Override - public void bindString(int index, String value) { - mBindingTypes[index] = STRING; - mStringBindings[index] = value; - } - - @Override - public void bindBlob(int index, byte[] value) { - mBindingTypes[index] = BLOB; - mBlobBindings[index] = value; - } - - @Override - public void close() { - // no-op. not calling release because it is internal API. - } - - /** - * Copies arguments from another RoomSQLiteQuery into this query. - * - * @param other The other query, which holds the arguments to be copied. - */ - public void copyArgumentsFrom(RoomSQLiteQuery other) { - int argCount = other.getArgCount() + 1; // +1 for the binding offsets - System.arraycopy(other.mBindingTypes, 0, mBindingTypes, 0, argCount); - System.arraycopy(other.mLongBindings, 0, mLongBindings, 0, argCount); - System.arraycopy(other.mStringBindings, 0, mStringBindings, 0, argCount); - System.arraycopy(other.mBlobBindings, 0, mBlobBindings, 0, argCount); - System.arraycopy(other.mDoubleBindings, 0, mDoubleBindings, 0, argCount); - } - - @Override - public void clearBindings() { - Arrays.fill(mBindingTypes, NULL); - Arrays.fill(mStringBindings, null); - Arrays.fill(mBlobBindings, null); - mQuery = null; - // no need to clear others - } - - private static final int NULL = 1; - private static final int LONG = 2; - private static final int DOUBLE = 3; - private static final int STRING = 4; - private static final int BLOB = 5; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({NULL, LONG, DOUBLE, STRING, BLOB}) - @interface Binding { - } -} diff --git a/app/src/main/java/androidx/room/RoomTrackingLiveData.java b/app/src/main/java/androidx/room/RoomTrackingLiveData.java deleted file mode 100644 index f6086584b5..0000000000 --- a/app/src/main/java/androidx/room/RoomTrackingLiveData.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - - -import android.annotation.SuppressLint; - -import androidx.annotation.MainThread; -import androidx.annotation.NonNull; -import androidx.annotation.WorkerThread; -import androidx.arch.core.executor.ArchTaskExecutor; -import androidx.lifecycle.LiveData; - -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * A LiveData implementation that closely works with {@link InvalidationTracker} to implement - * database drive {@link androidx.lifecycle.LiveData} queries that are strongly hold as long - * as they are active. - *

- * We need this extra handling for {@link androidx.lifecycle.LiveData} because when they are - * observed forever, there is no {@link androidx.lifecycle.Lifecycle} that will keep them in - * memory but they should stay. We cannot add-remove observer in {@link LiveData#onActive()}, - * {@link LiveData#onInactive()} because that would mean missing changes in between or doing an - * extra query on every UI rotation. - *

- * This {@link LiveData} keeps a weak observer to the {@link InvalidationTracker} but it is hold - * strongly by the {@link InvalidationTracker} as long as it is active. - */ -class RoomTrackingLiveData extends LiveData { - @SuppressWarnings("WeakerAccess") - final RoomDatabase mDatabase; - - @SuppressWarnings("WeakerAccess") - final boolean mInTransaction; - - @SuppressWarnings("WeakerAccess") - final Callable mComputeFunction; - - private final InvalidationLiveDataContainer mContainer; - - @SuppressWarnings("WeakerAccess") - final InvalidationTracker.Observer mObserver; - - @SuppressWarnings("WeakerAccess") - final AtomicBoolean mInvalid = new AtomicBoolean(true); - - @SuppressWarnings("WeakerAccess") - final AtomicBoolean mComputing = new AtomicBoolean(false); - - @SuppressWarnings("WeakerAccess") - final AtomicBoolean mRegisteredObserver = new AtomicBoolean(false); - - @SuppressWarnings("WeakerAccess") - final Runnable mRefreshRunnable = new Runnable() { - @WorkerThread - @Override - public void run() { - if (mRegisteredObserver.compareAndSet(false, true)) { - mDatabase.getInvalidationTracker().addWeakObserver(mObserver); - } - boolean computed; - do { - computed = false; - // compute can happen only in 1 thread but no reason to lock others. - if (mComputing.compareAndSet(false, true)) { - // as long as it is invalid, keep computing. - try { - T value = null; - while (mInvalid.compareAndSet(true, false)) { - computed = true; - try { - value = mComputeFunction.call(); - } catch (Exception e) { - eu.faircode.email.Log.w(e); - //throw new RuntimeException("Exception while computing database" - // + " live data.", e); - computed = false; - } - } - if (computed) { - postValue(value); - } - } finally { - // release compute lock - mComputing.set(false); - } - } - // check invalid after releasing compute lock to avoid the following scenario. - // Thread A runs compute() - // Thread A checks invalid, it is false - // Main thread sets invalid to true - // Thread B runs, fails to acquire compute lock and skips - // Thread A releases compute lock - // We've left invalid in set state. The check below recovers. - } while (computed && mInvalid.get()); - } - }; - - @SuppressWarnings("WeakerAccess") - final Runnable mInvalidationRunnable = new Runnable() { - @MainThread - @Override - public void run() { - boolean isActive = hasActiveObservers(); - if (mInvalid.compareAndSet(false, true)) { - if (isActive) { - getQueryExecutor().execute(mRefreshRunnable); - } - } - } - }; - @SuppressLint("RestrictedApi") - RoomTrackingLiveData( - RoomDatabase database, - InvalidationLiveDataContainer container, - boolean inTransaction, - Callable computeFunction, - String[] tableNames) { - mDatabase = database; - mInTransaction = inTransaction; - mComputeFunction = computeFunction; - mContainer = container; - mObserver = new InvalidationTracker.Observer(tableNames) { - @Override - public void onInvalidated(@NonNull Set tables) { - ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidationRunnable); - } - }; - } - - @Override - protected void onActive() { - super.onActive(); - mContainer.onActive(this); - getQueryExecutor().execute(mRefreshRunnable); - } - - @Override - protected void onInactive() { - super.onInactive(); - mContainer.onInactive(this); - } - - Executor getQueryExecutor() { - if (mInTransaction) { - return mDatabase.getTransactionExecutor(); - } else { - return mDatabase.getQueryExecutor(); - } - } -} diff --git a/app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java b/app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java deleted file mode 100644 index f8240ceed3..0000000000 --- a/app/src/main/java/androidx/room/SQLiteCopyOpenHelper.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import android.content.Context; -import android.os.Build; -import android.util.Log; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.room.util.CopyLock; -import androidx.room.util.DBUtil; -import androidx.room.util.FileUtil; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.sqlite.db.SupportSQLiteOpenHelper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; - -/** - * An open helper that will copy & open a pre-populated database if it doesn't exists in internal - * storage. - */ -class SQLiteCopyOpenHelper implements SupportSQLiteOpenHelper { - - @NonNull - private final Context mContext; - @Nullable - private final String mCopyFromAssetPath; - @Nullable - private final File mCopyFromFile; - private final int mDatabaseVersion; - @NonNull - private final SupportSQLiteOpenHelper mDelegate; - @Nullable - private DatabaseConfiguration mDatabaseConfiguration; - - private boolean mVerified; - - SQLiteCopyOpenHelper( - @NonNull Context context, - @Nullable String copyFromAssetPath, - @Nullable File copyFromFile, - int databaseVersion, - @NonNull SupportSQLiteOpenHelper supportSQLiteOpenHelper) { - mContext = context; - mCopyFromAssetPath = copyFromAssetPath; - mCopyFromFile = copyFromFile; - mDatabaseVersion = databaseVersion; - mDelegate = supportSQLiteOpenHelper; - } - - @Override - public String getDatabaseName() { - return mDelegate.getDatabaseName(); - } - - @Override - @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) - public void setWriteAheadLoggingEnabled(boolean enabled) { - mDelegate.setWriteAheadLoggingEnabled(enabled); - } - - @Override - public synchronized SupportSQLiteDatabase getWritableDatabase() { - if (!mVerified) { - verifyDatabaseFile(); - mVerified = true; - } - return mDelegate.getWritableDatabase(); - } - - @Override - public synchronized SupportSQLiteDatabase getReadableDatabase() { - if (!mVerified) { - verifyDatabaseFile(); - mVerified = true; - } - return mDelegate.getReadableDatabase(); - } - - @Override - public synchronized void close() { - mDelegate.close(); - mVerified = false; - } - - // Can't be constructor param because the factory is needed by the database builder which in - // turn is the one that actually builds the configuration. - void setDatabaseConfiguration(@Nullable DatabaseConfiguration databaseConfiguration) { - mDatabaseConfiguration = databaseConfiguration; - } - - private void verifyDatabaseFile() { - String databaseName = getDatabaseName(); - File databaseFile = mContext.getDatabasePath(databaseName); - boolean processLevelLock = mDatabaseConfiguration == null - || mDatabaseConfiguration.multiInstanceInvalidation; - CopyLock copyLock = new CopyLock(databaseName, mContext.getFilesDir(), processLevelLock); - try { - // Acquire a copy lock, this lock works across threads and processes, preventing - // concurrent copy attempts from occurring. - copyLock.lock(); - - if (!databaseFile.exists()) { - try { - // No database file found, copy and be done. - copyDatabaseFile(databaseFile); - return; - } catch (IOException e) { - throw new RuntimeException("Unable to copy database file.", e); - } - } - - if (mDatabaseConfiguration == null) { - return; - } - - // A database file is present, check if we need to re-copy it. - int currentVersion; - try { - currentVersion = DBUtil.readVersion(databaseFile); - } catch (IOException e) { - Log.w(Room.LOG_TAG, "Unable to read database version.", e); - return; - } - - if (currentVersion == mDatabaseVersion) { - return; - } - - if (mDatabaseConfiguration.isMigrationRequired(currentVersion, mDatabaseVersion)) { - // From the current version to the desired version a migration is required, i.e. - // we won't be performing a copy destructive migration. - return; - } - - if (mContext.deleteDatabase(databaseName)) { - try { - copyDatabaseFile(databaseFile); - } catch (IOException e) { - // We are more forgiving copying a database on a destructive migration since - // there is already a database file that can be opened. - Log.w(Room.LOG_TAG, "Unable to copy database file.", e); - } - } else { - Log.w(Room.LOG_TAG, "Failed to delete database file (" - + databaseName + ") for a copy destructive migration."); - } - } finally { - copyLock.unlock(); - } - } - - private void copyDatabaseFile(File destinationFile) throws IOException { - ReadableByteChannel input; - if (mCopyFromAssetPath != null) { - input = Channels.newChannel(mContext.getAssets().open(mCopyFromAssetPath)); - } else if (mCopyFromFile != null) { - input = new FileInputStream(mCopyFromFile).getChannel(); - } else { - throw new IllegalStateException("copyFromAssetPath and copyFromFile == null!"); - } - - // An intermediate file is used so that we never end up with a half-copied database file - // in the internal directory. - File intermediateFile = File.createTempFile( - "room-copy-helper", ".tmp", mContext.getCacheDir()); - intermediateFile.deleteOnExit(); - FileChannel output = new FileOutputStream(intermediateFile).getChannel(); - FileUtil.copy(input, output); - - File parent = destinationFile.getParentFile(); - if (parent != null && !parent.exists() && !parent.mkdirs()) { - throw new IOException("Failed to create directories for " - + destinationFile.getAbsolutePath()); - } - - if (!intermediateFile.renameTo(destinationFile)) { - throw new IOException("Failed to move intermediate file (" - + intermediateFile.getAbsolutePath() + ") to destination (" - + destinationFile.getAbsolutePath() + ")."); - } - } -} diff --git a/app/src/main/java/androidx/room/SQLiteCopyOpenHelperFactory.java b/app/src/main/java/androidx/room/SQLiteCopyOpenHelperFactory.java deleted file mode 100644 index 22178e80ee..0000000000 --- a/app/src/main/java/androidx/room/SQLiteCopyOpenHelperFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.sqlite.db.SupportSQLiteOpenHelper; - -import java.io.File; - -/** - * Implementation of {@link SupportSQLiteOpenHelper.Factory} that creates - * {@link SQLiteCopyOpenHelper}. - */ -class SQLiteCopyOpenHelperFactory implements SupportSQLiteOpenHelper.Factory { - - @Nullable - private final String mCopyFromAssetPath; - @Nullable - private final File mCopyFromFile; - @NonNull - private final SupportSQLiteOpenHelper.Factory mDelegate; - - SQLiteCopyOpenHelperFactory( - @Nullable String copyFromAssetPath, - @Nullable File copyFromFile, - @NonNull SupportSQLiteOpenHelper.Factory factory) { - mCopyFromAssetPath = copyFromAssetPath; - mCopyFromFile = copyFromFile; - mDelegate = factory; - } - - @Override - public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) { - return new SQLiteCopyOpenHelper( - configuration.context, - mCopyFromAssetPath, - mCopyFromFile, - configuration.callback.version, - mDelegate.create(configuration)); - } -} diff --git a/app/src/main/java/androidx/room/SharedSQLiteStatement.java b/app/src/main/java/androidx/room/SharedSQLiteStatement.java deleted file mode 100644 index 20c06c8b50..0000000000 --- a/app/src/main/java/androidx/room/SharedSQLiteStatement.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.room; - -import androidx.annotation.RestrictTo; -import androidx.sqlite.db.SupportSQLiteStatement; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Represents a prepared SQLite state that can be re-used multiple times. - *

- * This class is used by generated code. After it is used, {@code release} must be called so that - * it can be used by other threads. - *

- * To avoid re-entry even within the same thread, this class allows only 1 time access to the shared - * statement until it is released. - * - * @hide - */ -@SuppressWarnings({"WeakerAccess", "unused"}) -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public abstract class SharedSQLiteStatement { - private final AtomicBoolean mLock = new AtomicBoolean(false); - - private final RoomDatabase mDatabase; - private volatile SupportSQLiteStatement mStmt; - - /** - * Creates an SQLite prepared statement that can be re-used across threads. If it is in use, - * it automatically creates a new one. - * - * @param database The database to create the statement in. - */ - public SharedSQLiteStatement(RoomDatabase database) { - mDatabase = database; - } - - /** - * Create the query. - * - * @return The SQL query to prepare. - */ - protected abstract String createQuery(); - - protected void assertNotMainThread() { - mDatabase.assertNotMainThread(); - } - - private SupportSQLiteStatement createNewStatement() { - String query = createQuery(); - return mDatabase.compileStatement(query); - } - - private SupportSQLiteStatement getStmt(boolean canUseCached) { - final SupportSQLiteStatement stmt; - if (canUseCached) { - if (mStmt == null) { - mStmt = createNewStatement(); - } - stmt = mStmt; - } else { - // it is in use, create a one off statement - stmt = createNewStatement(); - } - return stmt; - } - - /** - * Call this to get the statement. Must call {@link #release(SupportSQLiteStatement)} once done. - */ - public SupportSQLiteStatement acquire() { - assertNotMainThread(); - return getStmt(mLock.compareAndSet(false, true)); - } - - /** - * Must call this when statement will not be used anymore. - * - * @param statement The statement that was returned from acquire. - */ - public void release(SupportSQLiteStatement statement) { - if (statement == mStmt) { - mLock.set(false); - } - } -} diff --git a/app/src/main/java/androidx/room/TransactionExecutor.java b/app/src/main/java/androidx/room/TransactionExecutor.java deleted file mode 100644 index 6a6bc1260d..0000000000 --- a/app/src/main/java/androidx/room/TransactionExecutor.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room; - -import androidx.annotation.NonNull; - -import java.util.ArrayDeque; -import java.util.concurrent.Executor; - -/** - * Executor wrapper for performing database transactions serially. - *

- * Since database transactions are exclusive, this executor ensures that transactions are performed - * in-order and one at a time, preventing threads from blocking each other when multiple concurrent - * transactions are attempted. - */ -class TransactionExecutor implements Executor { - - private final Executor mExecutor; - private final ArrayDeque mTasks = new ArrayDeque<>(); - private Runnable mActive; - - TransactionExecutor(@NonNull Executor executor) { - mExecutor = executor; - } - - public synchronized void execute(final Runnable command) { - mTasks.offer(new Runnable() { - public void run() { - try { - command.run(); - } finally { - scheduleNext(); - } - } - }); - if (mActive == null) { - scheduleNext(); - } - } - - @SuppressWarnings("WeakerAccess") - synchronized void scheduleNext() { - if ((mActive = mTasks.poll()) != null) { - mExecutor.execute(mActive); - } - } -} diff --git a/app/src/main/java/androidx/room/migration/Migration.java b/app/src/main/java/androidx/room/migration/Migration.java deleted file mode 100644 index 4aa7a7e86a..0000000000 --- a/app/src/main/java/androidx/room/migration/Migration.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.migration; - -import androidx.annotation.NonNull; -import androidx.sqlite.db.SupportSQLiteDatabase; - -/** - * Base class for a database migration. - *

- * Each migration can move between 2 versions that are defined by {@link #startVersion} and - * {@link #endVersion}. - *

- * A migration can handle more than 1 version (e.g. if you have a faster path to choose when - * going version 3 to 5 without going to version 4). If Room opens a database at version - * 3 and latest version is >= 5, Room will use the migration object that can migrate from - * 3 to 5 instead of 3 to 4 and 4 to 5. - *

- * If there are not enough migrations provided to move from the current version to the latest - * version, Room will clear the database and recreate so even if you have no changes between 2 - * versions, you should still provide a Migration object to the builder. - */ -public abstract class Migration { - public final int startVersion; - public final int endVersion; - - /** - * Creates a new migration between {@code startVersion} and {@code endVersion}. - * - * @param startVersion The start version of the database. - * @param endVersion The end version of the database after this migration is applied. - */ - public Migration(int startVersion, int endVersion) { - this.startVersion = startVersion; - this.endVersion = endVersion; - } - - /** - * Should run the necessary migrations. - *

- * This class cannot access any generated Dao in this method. - *

- * This method is already called inside a transaction and that transaction might actually be a - * composite transaction of all necessary {@code Migration}s. - * - * @param database The database instance - */ - public abstract void migrate(@NonNull SupportSQLiteDatabase database); -} diff --git a/app/src/main/java/androidx/room/package-info.java b/app/src/main/java/androidx/room/package-info.java deleted file mode 100644 index 3a14e974ff..0000000000 --- a/app/src/main/java/androidx/room/package-info.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Room is a Database Object Mapping library that makes it easy to access database on Android - * applications. - *

- * Rather than hiding the details of SQLite, Room tries to embrace them by providing convenient APIs - * to query the database and also verify such queries at compile time. This allows you to access - * the full power of SQLite while having the type safety provided by Java SQL query builders. - *

- * There are 3 major components in Room. - *

- * Below is a sample of a simple database. - *
- * // File: Song.java
- * {@literal @}Entity
- * public class User {
- *   {@literal @}PrimaryKey
- *   private int id;
- *   private String name;
- *   {@literal @}ColumnInfo(name = "release_year")
- *   private int releaseYear;
- *   // getters and setters are ignored for brevity but they are required for Room to work.
- * }
- * // File: SongDao.java
- * {@literal @}Dao
- * public interface SongDao {
- *   {@literal @}Query("SELECT * FROM song")
- *   List<Song> loadAll();
- *   {@literal @}Query("SELECT * FROM song WHERE id IN (:songIds)")
- *   List<Song> loadAllBySongId(int... songIds);
- *   {@literal @}Query("SELECT * FROM song WHERE name LIKE :name AND release_year = :year LIMIT 1")
- *   Song loadOneByNameAndReleaseYear(String first, int year);
- *   {@literal @}Insert
- *   void insertAll(Song... songs);
- *   {@literal @}Delete
- *   void delete(Song song);
- * }
- * // File: MusicDatabase.java
- * {@literal @}Database(entities = {Song.java})
- * public abstract class MusicDatabase extends RoomDatabase {
- *   public abstract SongDao userDao();
- * }
- * 
- * You can create an instance of {@code MusicDatabase} as follows: - *
- * MusicDatabase db = Room
- *     .databaseBuilder(getApplicationContext(), MusicDatabase.class, "database-name")
- *     .build();
- * 
- * Since Room verifies your queries at compile time, it also detects information about which tables - * are accessed by the query or what columns are present in the response. - *

- * You can observe a particular table for changes using the - * {@link androidx.room.InvalidationTracker InvalidationTracker} class which you can acquire via - * {@link androidx.room.RoomDatabase#getInvalidationTracker() - * RoomDatabase.getInvalidationTracker}. - *

- * For convenience, Room allows you to return {@link androidx.lifecycle.LiveData LiveData} from - * {@link androidx.room.Query Query} methods. It will automatically observe the related tables as - * long as the {@code LiveData} has active observers. - *

- * // This live data will automatically dispatch changes as the database changes.
- * {@literal @}Query("SELECT * FROM song ORDER BY name LIMIT 5")
- * LiveData<Song> loadFirstFiveSongs();
- * 
- *

- * You can also return arbitrary data objects from your query results as long as the fields in the - * object match the list of columns in the query response. This makes it very easy to write - * applications that drive the UI from persistent storage. - *

- * class IdAndSongHeader {
- *   int id;
- *   {@literal @}ColumnInfo(name = "header")
- *   String header;
- * }
- * // DAO
- * {@literal @}Query("SELECT id, name || '-' || release_year AS header FROM user")
- * public IdAndSongHeader[] loadSongHeaders();
- * 
- * If there is a mismatch between the query result and the POJO, Room will print a warning during - * compilation. - *

- * Please see the documentation of individual classes for details. - */ -package androidx.room; diff --git a/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java b/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java deleted file mode 100644 index 156ee24ac7..0000000000 --- a/app/src/main/java/androidx/room/paging/LimitOffsetDataSource.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.paging; - -import android.database.Cursor; - -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; -import androidx.paging.PositionalDataSource; -import androidx.room.InvalidationTracker; -import androidx.room.RoomDatabase; -import androidx.room.RoomSQLiteQuery; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * A simple data source implementation that uses Limit & Offset to page the query. - *

- * This is NOT the most efficient way to do paging on SQLite. It is - * recommended to use an indexed - * ORDER BY statement but that requires a more complex API. This solution is technically equal to - * receiving a {@link Cursor} from a large query but avoids the need to manually manage it, and - * never returns inconsistent data if it is invalidated. - * - * @param Data type returned by the data source. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public abstract class LimitOffsetDataSource extends PositionalDataSource { - private final RoomSQLiteQuery mSourceQuery; - private final String mCountQuery; - private final String mLimitOffsetQuery; - private final RoomDatabase mDb; - @SuppressWarnings("FieldCanBeLocal") - private final InvalidationTracker.Observer mObserver; - private final boolean mInTransaction; - - protected LimitOffsetDataSource(RoomDatabase db, SupportSQLiteQuery query, - boolean inTransaction, String... tables) { - this(db, RoomSQLiteQuery.copyFrom(query), inTransaction, tables); - } - - protected LimitOffsetDataSource(RoomDatabase db, RoomSQLiteQuery query, - boolean inTransaction, String... tables) { - mDb = db; - mSourceQuery = query; - mInTransaction = inTransaction; - mCountQuery = "SELECT COUNT(*) FROM ( " + mSourceQuery.getSql() + " )"; - mLimitOffsetQuery = "SELECT * FROM ( " + mSourceQuery.getSql() + " ) LIMIT ? OFFSET ?"; - mObserver = new InvalidationTracker.Observer(tables) { - @Override - public void onInvalidated(@NonNull Set tables) { - invalidate(); - } - }; - db.getInvalidationTracker().addWeakObserver(mObserver); - } - - /** - * Count number of rows query can return - * - * @hide - */ - @SuppressWarnings("WeakerAccess") - public int countItems() { - final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mCountQuery, - mSourceQuery.getArgCount()); - sqLiteQuery.copyArgumentsFrom(mSourceQuery); - Cursor cursor = mDb.query(sqLiteQuery); - try { - if (cursor.moveToFirst()) { - return cursor.getInt(0); - } - return 0; - } finally { - cursor.close(); - sqLiteQuery.release(); - } - } - - @Override - public boolean isInvalid() { - mDb.getInvalidationTracker().refreshVersionsSync(); - return super.isInvalid(); - } - - @SuppressWarnings("WeakerAccess") - protected abstract List convertRows(Cursor cursor); - - @SuppressWarnings("deprecation") - @Override - public void loadInitial(@NonNull LoadInitialParams params, - @NonNull LoadInitialCallback callback) { - List list = Collections.emptyList(); - int totalCount = 0; - int firstLoadPosition = 0; - RoomSQLiteQuery sqLiteQuery = null; - Cursor cursor = null; - mDb.beginTransaction(); - try { - totalCount = countItems(); - if (totalCount != 0) { - // bound the size requested, based on known count - firstLoadPosition = computeInitialLoadPosition(params, totalCount); - int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount); - - sqLiteQuery = getSQLiteQuery(firstLoadPosition, firstLoadSize); - cursor = mDb.query(sqLiteQuery); - List rows = convertRows(cursor); - mDb.setTransactionSuccessful(); - list = rows; - } - } finally { - if (cursor != null) { - cursor.close(); - } - mDb.endTransaction(); - if (sqLiteQuery != null) { - sqLiteQuery.release(); - } - } - - callback.onResult(list, firstLoadPosition, totalCount); - } - - @Override - public void loadRange(@NonNull LoadRangeParams params, - @NonNull LoadRangeCallback callback) { - callback.onResult(loadRange(params.startPosition, params.loadSize)); - } - - /** - * Return the rows from startPos to startPos + loadCount - * - * @hide - */ - @SuppressWarnings("deprecation") - @NonNull - public List loadRange(int startPosition, int loadCount) { - final RoomSQLiteQuery sqLiteQuery = getSQLiteQuery(startPosition, loadCount); - if (mInTransaction) { - mDb.beginTransaction(); - Cursor cursor = null; - //noinspection TryFinallyCanBeTryWithResources - try { - cursor = mDb.query(sqLiteQuery); - List rows = convertRows(cursor); - mDb.setTransactionSuccessful(); - return rows; - } finally { - if (cursor != null) { - cursor.close(); - } - mDb.endTransaction(); - sqLiteQuery.release(); - } - } else { - Cursor cursor = mDb.query(sqLiteQuery); - //noinspection TryFinallyCanBeTryWithResources - try { - return convertRows(cursor); - } finally { - cursor.close(); - sqLiteQuery.release(); - } - } - } - - private RoomSQLiteQuery getSQLiteQuery(int startPosition, int loadCount) { - final RoomSQLiteQuery sqLiteQuery = RoomSQLiteQuery.acquire(mLimitOffsetQuery, - mSourceQuery.getArgCount() + 2); - sqLiteQuery.copyArgumentsFrom(mSourceQuery); - sqLiteQuery.bindLong(sqLiteQuery.getArgCount() - 1, loadCount); - sqLiteQuery.bindLong(sqLiteQuery.getArgCount(), startPosition); - return sqLiteQuery; - } -} diff --git a/app/src/main/java/androidx/room/util/CopyLock.java b/app/src/main/java/androidx/room/util/CopyLock.java deleted file mode 100644 index 3750315f34..0000000000 --- a/app/src/main/java/androidx/room/util/CopyLock.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Utility class for in-process and multi-process key-based lock mechanism for safely copying - * database files. - *

- * Acquiring the lock will be quick if no other thread or process has a lock with the same key. - * But if the lock is already held then acquiring it will block, until the other thread or process - * releases the lock. Note that the key and lock directory must be the same to achieve - * synchronization. - *

- * Locking is done via two levels: - *

    - *
  1. - * Thread locking within the same JVM process is done via a map of String key to ReentrantLock - * objects. - *
  2. - * Multi-process locking is done via a dummy file whose name contains the key and FileLock - * objects. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class CopyLock { - - // in-process lock map - private static final Map sThreadLocks = new HashMap<>(); - - private final File mCopyLockFile; - private final Lock mThreadLock; - private final boolean mFileLevelLock; - private FileChannel mLockChannel; - - /** - * Creates a lock with {@code name} and using {@code lockDir} as the directory for the - * lock files. - * @param name the name of this lock. - * @param lockDir the directory where the lock files will be located. - * @param processLock whether to use file for process level locking or not. - */ - public CopyLock(@NonNull String name, @NonNull File lockDir, boolean processLock) { - mCopyLockFile = new File(lockDir, name + ".lck"); - mThreadLock = getThreadLock(mCopyLockFile.getAbsolutePath()); - mFileLevelLock = processLock; - } - - /** - * Attempts to grab the lock, blocking if already held by another thread or process. - */ - public void lock() { - mThreadLock.lock(); - if (mFileLevelLock) { - try { - mLockChannel = new FileOutputStream(mCopyLockFile).getChannel(); - mLockChannel.lock(); - } catch (IOException e) { - throw new IllegalStateException("Unable to grab copy lock.", e); - } - } - } - - /** - * Releases the lock. - */ - public void unlock() { - if (mLockChannel != null) { - try { - mLockChannel.close(); - } catch (IOException ignored) { } - } - mThreadLock.unlock(); - } - - private static Lock getThreadLock(String key) { - synchronized (sThreadLocks) { - Lock threadLock = sThreadLocks.get(key); - if (threadLock == null) { - threadLock = new ReentrantLock(); - sThreadLocks.put(key, threadLock); - } - return threadLock; - } - } -} diff --git a/app/src/main/java/androidx/room/util/CursorUtil.java b/app/src/main/java/androidx/room/util/CursorUtil.java deleted file mode 100644 index 05b8c59170..0000000000 --- a/app/src/main/java/androidx/room/util/CursorUtil.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import android.database.Cursor; -import android.database.MatrixCursor; - -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; - -/** - * Cursor utilities for Room - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class CursorUtil { - - /** - * Copies the given cursor into a in-memory cursor and then closes it. - *

    - * This is useful for iterating over a cursor multiple times without the cost of JNI while - * reading or IO while filling the window at the expense of memory consumption. - * - * @param c the cursor to copy. - * @return a new cursor containing the same data as the given cursor. - */ - @NonNull - public static Cursor copyAndClose(@NonNull Cursor c) { - final MatrixCursor matrixCursor; - try { - matrixCursor = new MatrixCursor(c.getColumnNames(), c.getCount()); - while (c.moveToNext()) { - final Object[] row = new Object[c.getColumnCount()]; - for (int i = 0; i < c.getColumnCount(); i++) { - switch (c.getType(i)) { - case Cursor.FIELD_TYPE_NULL: - row[i] = null; - break; - case Cursor.FIELD_TYPE_INTEGER: - row[i] = c.getLong(i); - break; - case Cursor.FIELD_TYPE_FLOAT: - row[i] = c.getDouble(i); - break; - case Cursor.FIELD_TYPE_STRING: - row[i] = c.getString(i); - break; - case Cursor.FIELD_TYPE_BLOB: - row[i] = c.getBlob(i); - break; - default: - throw new IllegalStateException(); - } - } - matrixCursor.addRow(row); - } - } finally { - c.close(); - } - return matrixCursor; - } - - /** - * Patches {@link Cursor#getColumnIndex(String)} to work around issues on older devices. - * If the column is not found, it retries with the specified name surrounded by backticks. - * - * @param c The cursor. - * @param name The name of the target column. - * @return The index of the column, or -1 if not found. - */ - public static int getColumnIndex(@NonNull Cursor c, @NonNull String name) { - final int index = c.getColumnIndex(name); - if (index >= 0) { - return index; - } - return c.getColumnIndex("`" + name + "`"); - } - - /** - * Patches {@link Cursor#getColumnIndexOrThrow(String)} to work around issues on older devices. - * If the column is not found, it retries with the specified name surrounded by backticks. - * - * @param c The cursor. - * @param name The name of the target column. - * @return The index of the column. - * @throws IllegalArgumentException if the column does not exist. - */ - public static int getColumnIndexOrThrow(@NonNull Cursor c, @NonNull String name) { - final int index = c.getColumnIndex(name); - if (index >= 0) { - return index; - } - return c.getColumnIndexOrThrow("`" + name + "`"); - } - - private CursorUtil() { - } -} diff --git a/app/src/main/java/androidx/room/util/DBUtil.java b/app/src/main/java/androidx/room/util/DBUtil.java deleted file mode 100644 index f7afd265a3..0000000000 --- a/app/src/main/java/androidx/room/util/DBUtil.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import android.database.AbstractWindowedCursor; -import android.database.Cursor; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; -import androidx.room.RoomDatabase; -import androidx.sqlite.db.SupportSQLiteDatabase; -import androidx.sqlite.db.SupportSQLiteQuery; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.List; - -/** - * Database utilities for Room - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class DBUtil { - - /** - * Performs the SQLiteQuery on the given database. - *

    - * This util method encapsulates copying the cursor if the {@code maybeCopy} parameter is - * {@code true} and either the api level is below a certain threshold or the full result of the - * query does not fit in a single window. - * - * @param db The database to perform the query on. - * @param sqLiteQuery The query to perform. - * @param maybeCopy True if the result cursor should maybe be copied, false otherwise. - * @return Result of the query. - */ - @NonNull - public static Cursor query(RoomDatabase db, SupportSQLiteQuery sqLiteQuery, boolean maybeCopy) { - final Cursor cursor = db.query(sqLiteQuery); - if (maybeCopy && cursor instanceof AbstractWindowedCursor) { - AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor; - int rowsInCursor = windowedCursor.getCount(); // Should fill the window. - int rowsInWindow; - if (windowedCursor.hasWindow()) { - rowsInWindow = windowedCursor.getWindow().getNumRows(); - } else { - rowsInWindow = rowsInCursor; - } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || rowsInWindow < rowsInCursor) { - return CursorUtil.copyAndClose(windowedCursor); - } - } - - return cursor; - } - - /** - * Drops all FTS content sync triggers created by Room. - *

    - * FTS content sync triggers created by Room are those that are found in the sqlite_master table - * who's names start with 'room_fts_content_sync_'. - * - * @param db The database. - */ - public static void dropFtsSyncTriggers(SupportSQLiteDatabase db) { - List existingTriggers = new ArrayList<>(); - Cursor cursor = db.query("SELECT name FROM sqlite_master WHERE type = 'trigger'"); - //noinspection TryFinallyCanBeTryWithResources - try { - while (cursor.moveToNext()) { - existingTriggers.add(cursor.getString(0)); - } - } finally { - cursor.close(); - } - - for (String triggerName : existingTriggers) { - if (triggerName.startsWith("room_fts_content_sync_")) { - db.execSQL("DROP TRIGGER IF EXISTS " + triggerName); - } - } - } - - /** - * Reads the user version number out of the database header from the given file. - * - * @param databaseFile the database file. - * @return the database version - * @throws IOException if something goes wrong reading the file, such as bad database header or - * missing permissions. - * - * @see User Version - * Number. - */ - public static int readVersion(@NonNull File databaseFile) throws IOException { - FileChannel input = null; - try { - ByteBuffer buffer = ByteBuffer.allocate(4); - input = new FileInputStream(databaseFile).getChannel(); - input.tryLock(60, 4, true); - input.position(60); - int read = input.read(buffer); - if (read != 4) { - throw new IOException("Bad database header, unable to read 4 bytes at offset 60"); - } - buffer.rewind(); - return buffer.getInt(); // ByteBuffer is big-endian by default - } finally { - if (input != null) { - input.close(); - } - } - } - - private DBUtil() { - } -} diff --git a/app/src/main/java/androidx/room/util/FileUtil.java b/app/src/main/java/androidx/room/util/FileUtil.java deleted file mode 100644 index a221ff741a..0000000000 --- a/app/src/main/java/androidx/room/util/FileUtil.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import android.annotation.SuppressLint; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; - -/** - * File utilities for Room - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class FileUtil { - - /** - * Copies data from the input channel to the output file channel. - * - * @param input the input channel to copy. - * @param output the output channel to copy. - * @throws IOException if there is an I/O error. - */ - @SuppressLint("LambdaLast") - public static void copy(@NonNull ReadableByteChannel input, @NonNull FileChannel output) - throws IOException { - try { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { - output.transferFrom(input, 0, Long.MAX_VALUE); - } else { - InputStream inputStream = Channels.newInputStream(input); - OutputStream outputStream = Channels.newOutputStream(output); - int length; - byte[] buffer = new byte[1024 * 4]; - while ((length = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, length); - } - } - output.force(false); - } finally { - input.close(); - output.close(); - } - } - - private FileUtil() { - } -} diff --git a/app/src/main/java/androidx/room/util/FtsTableInfo.java b/app/src/main/java/androidx/room/util/FtsTableInfo.java deleted file mode 100644 index c076a0238b..0000000000 --- a/app/src/main/java/androidx/room/util/FtsTableInfo.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import android.database.Cursor; - -import androidx.annotation.RestrictTo; -import androidx.annotation.VisibleForTesting; -import androidx.sqlite.db.SupportSQLiteDatabase; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * A data class that holds the information about an FTS table. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class FtsTableInfo { - - // A set of valid FTS Options - private static final String[] FTS_OPTIONS = new String[] { - "tokenize=", "compress=", "content=", "languageid=", "matchinfo=", "notindexed=", - "order=", "prefix=", "uncompress="}; - - /** - * The table name - */ - public final String name; - - /** - * The column names - */ - public final Set columns; - - /** - * The set of options. Each value in the set contains the option in the following format: - * <key>=<value>. - */ - public final Set options; - - public FtsTableInfo(String name, Set columns, Set options) { - this.name = name; - this.columns = columns; - this.options = options; - } - - public FtsTableInfo(String name, Set columns, String createSql) { - this.name = name; - this.columns = columns; - this.options = parseOptions(createSql); - } - - /** - * Reads the table information from the given database. - * - * @param database The database to read the information from. - * @param tableName The table name. - * @return A FtsTableInfo containing the columns and options for the provided table name. - */ - public static FtsTableInfo read(SupportSQLiteDatabase database, String tableName) { - Set columns = readColumns(database, tableName); - Set options = readOptions(database, tableName); - - return new FtsTableInfo(tableName, columns, options); - } - - @SuppressWarnings("TryFinallyCanBeTryWithResources") - private static Set readColumns(SupportSQLiteDatabase database, String tableName) { - Cursor cursor = database.query("PRAGMA table_info(`" + tableName + "`)"); - Set columns = new HashSet<>(); - try { - if (cursor.getColumnCount() > 0) { - int nameIndex = cursor.getColumnIndex("name"); - while (cursor.moveToNext()) { - columns.add(cursor.getString(nameIndex)); - } - } - } finally { - cursor.close(); - } - return columns; - } - - @SuppressWarnings("TryFinallyCanBeTryWithResources") - private static Set readOptions(SupportSQLiteDatabase database, String tableName) { - String sql = ""; - Cursor cursor = database.query( - "SELECT * FROM sqlite_master WHERE `name` = '" + tableName + "'"); - try { - if (cursor.moveToFirst()) { - sql = cursor.getString(cursor.getColumnIndexOrThrow("sql")); - } - } finally { - cursor.close(); - } - return parseOptions(sql); - } - - /** - * Parses FTS options from the create statement of an FTS table. - * - * This method assumes the given create statement is a valid well-formed SQLite statement as - * defined in the CREATE VIRTUAL TABLE - * syntax diagram. - * - * @param createStatement the "CREATE VIRTUAL TABLE" statement. - * @return the set of FTS option key and values in the create statement. - */ - @VisibleForTesting - @SuppressWarnings("WeakerAccess") /* synthetic access */ - static Set parseOptions(String createStatement) { - if (createStatement.isEmpty()) { - return new HashSet<>(); - } - - // Module arguments are within the parenthesis followed by the module name. - String argsString = createStatement.substring( - createStatement.indexOf('(') + 1, - createStatement.lastIndexOf(')')); - - // Split the module argument string by the comma delimiter, keeping track of quotation so - // so that if the delimiter is found within a string literal we don't substring at the wrong - // index. SQLite supports four ways of quoting keywords, see: - // https://www.sqlite.org/lang_keywords.html - List args = new ArrayList<>(); - ArrayDeque quoteStack = new ArrayDeque<>(); - int lastDelimiterIndex = -1; - for (int i = 0; i < argsString.length(); i++) { - char c = argsString.charAt(i); - switch (c) { - case '\'': - case '"': - case '`': - if (quoteStack.isEmpty()) { - quoteStack.push(c); - } else if (quoteStack.peek() == c) { - quoteStack.pop(); - } - break; - case '[': - if (quoteStack.isEmpty()) { - quoteStack.push(c); - } - break; - case ']': - if (!quoteStack.isEmpty() && quoteStack.peek() == '[') { - quoteStack.pop(); - } - break; - case ',': - if (quoteStack.isEmpty()) { - args.add(argsString.substring(lastDelimiterIndex + 1, i).trim()); - lastDelimiterIndex = i; - } - break; - } - } - args.add(argsString.substring(lastDelimiterIndex + 1).trim()); // Add final argument. - - // Match args against valid options, otherwise they are column definitions. - HashSet options = new HashSet<>(); - for (String arg : args) { - for (String validOption : FTS_OPTIONS) { - if (arg.startsWith(validOption)) { - options.add(arg); - } - } - } - - return options; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - FtsTableInfo that = (FtsTableInfo) o; - - if (name != null ? !name.equals(that.name) : that.name != null) return false; - if (columns != null ? !columns.equals(that.columns) : that.columns != null) return false; - return options != null ? options.equals(that.options) : that.options == null; - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (columns != null ? columns.hashCode() : 0); - result = 31 * result + (options != null ? options.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "FtsTableInfo{" - + "name='" + name + '\'' - + ", columns=" + columns - + ", options=" + options - + '}'; - } -} diff --git a/app/src/main/java/androidx/room/util/SneakyThrow.java b/app/src/main/java/androidx/room/util/SneakyThrow.java deleted file mode 100644 index 76968ab344..0000000000 --- a/app/src/main/java/androidx/room/util/SneakyThrow.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; - -/** - * Java 8 Sneaky Throw technique. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public class SneakyThrow { - - /** - * Re-throws a checked exception as if it was a runtime exception without wrapping it. - * - * @param e the exception to re-throw. - */ - public static void reThrow(@NonNull Exception e) { - sneakyThrow(e); - } - - @SuppressWarnings("unchecked") - private static void sneakyThrow(@NonNull Throwable e) throws E { - throw (E) e; - } - - private SneakyThrow() { - - } -} diff --git a/app/src/main/java/androidx/room/util/StringUtil.java b/app/src/main/java/androidx/room/util/StringUtil.java deleted file mode 100644 index 2b30eb64de..0000000000 --- a/app/src/main/java/androidx/room/util/StringUtil.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import android.util.Log; - -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; - -import java.util.ArrayList; -import java.util.List; -import java.util.StringTokenizer; - -/** - * @hide - * - * String utilities for Room - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class StringUtil { - - @SuppressWarnings("unused") - public static final String[] EMPTY_STRING_ARRAY = new String[0]; - /** - * Returns a new StringBuilder to be used while producing SQL queries. - * - * @return A new or recycled StringBuilder - */ - public static StringBuilder newStringBuilder() { - // TODO pool: - return new StringBuilder(); - } - - /** - * Adds bind variable placeholders (?) to the given string. Each placeholder is separated - * by a comma. - * - * @param builder The StringBuilder for the query - * @param count Number of placeholders - */ - public static void appendPlaceholders(StringBuilder builder, int count) { - for (int i = 0; i < count; i++) { - builder.append("?"); - if (i < count - 1) { - builder.append(","); - } - } - } - /** - * Splits a comma separated list of integers to integer list. - *

    - * If an input is malformed, it is omitted from the result. - * - * @param input Comma separated list of integers. - * @return A List containing the integers or null if the input is null. - */ - @Nullable - public static List splitToIntList(@Nullable String input) { - if (input == null) { - return null; - } - List result = new ArrayList<>(); - StringTokenizer tokenizer = new StringTokenizer(input, ","); - while (tokenizer.hasMoreElements()) { - final String item = tokenizer.nextToken(); - try { - result.add(Integer.parseInt(item)); - } catch (NumberFormatException ex) { - Log.e("ROOM", "Malformed integer list", ex); - } - } - return result; - } - - /** - * Joins the given list of integers into a comma separated list. - * - * @param input The list of integers. - * @return Comma separated string composed of integers in the list. If the list is null, return - * value is null. - */ - @Nullable - public static String joinIntoString(@Nullable List input) { - if (input == null) { - return null; - } - - final int size = input.size(); - if (size == 0) { - return ""; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < size; i++) { - sb.append(Integer.toString(input.get(i))); - if (i < size - 1) { - sb.append(","); - } - } - return sb.toString(); - } - - private StringUtil() { - } -} diff --git a/app/src/main/java/androidx/room/util/TableInfo.java b/app/src/main/java/androidx/room/util/TableInfo.java deleted file mode 100644 index a28aea49d3..0000000000 --- a/app/src/main/java/androidx/room/util/TableInfo.java +++ /dev/null @@ -1,665 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import android.database.Cursor; -import android.os.Build; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.room.ColumnInfo; -import androidx.sqlite.db.SupportSQLiteDatabase; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/** - * A data class that holds the information about a table. - *

    - * It directly maps to the result of {@code PRAGMA table_info()}. Check the - * PRAGMA table_info - * documentation for more details. - *

    - * Even though SQLite column names are case insensitive, this class uses case sensitive matching. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -@SuppressWarnings({"WeakerAccess", "unused", "TryFinallyCanBeTryWithResources", - "SimplifiableIfStatement"}) -// if you change this class, you must change TableInfoWriter.kt -public class TableInfo { - - /** - * Identifies from where the info object was created. - */ - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {CREATED_FROM_UNKNOWN, CREATED_FROM_ENTITY, CREATED_FROM_DATABASE}) - @interface CreatedFrom { - } - - /** - * Identifier for when the info is created from an unknown source. - */ - public static final int CREATED_FROM_UNKNOWN = 0; - - /** - * Identifier for when the info is created from an entity definition, such as generated code - * by the compiler or at runtime from a schema bundle, parsed from a schema JSON file. - */ - public static final int CREATED_FROM_ENTITY = 1; - - /** - * Identifier for when the info is created from the database itself, reading information from a - * PRAGMA, such as table_info. - */ - public static final int CREATED_FROM_DATABASE = 2; - - /** - * The table name. - */ - public final String name; - /** - * Unmodifiable map of columns keyed by column name. - */ - public final Map columns; - - public final Set foreignKeys; - - /** - * Sometimes, Index information is not available (older versions). If so, we skip their - * verification. - */ - @Nullable - public final Set indices; - - @SuppressWarnings("unused") - public TableInfo(String name, Map columns, Set foreignKeys, - Set indices) { - this.name = name; - this.columns = Collections.unmodifiableMap(columns); - this.foreignKeys = Collections.unmodifiableSet(foreignKeys); - this.indices = indices == null ? null : Collections.unmodifiableSet(indices); - } - - /** - * For backward compatibility with dbs created with older versions. - */ - @SuppressWarnings("unused") - public TableInfo(String name, Map columns, Set foreignKeys) { - this(name, columns, foreignKeys, Collections.emptySet()); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - TableInfo tableInfo = (TableInfo) o; - - if (name != null ? !name.equals(tableInfo.name) : tableInfo.name != null) return false; - if (columns != null ? !columns.equals(tableInfo.columns) : tableInfo.columns != null) { - return false; - } - if (foreignKeys != null ? !foreignKeys.equals(tableInfo.foreignKeys) - : tableInfo.foreignKeys != null) { - return false; - } - if (indices == null || tableInfo.indices == null) { - // if one us is missing index information, seems like we couldn't acquire the - // information so we better skip. - return true; - } - return indices.equals(tableInfo.indices); - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (columns != null ? columns.hashCode() : 0); - result = 31 * result + (foreignKeys != null ? foreignKeys.hashCode() : 0); - // skip index, it is not reliable for comparison. - return result; - } - - @Override - public String toString() { - return "TableInfo{" - + "name='" + name + '\'' - + ", columns=" + columns - + ", foreignKeys=" + foreignKeys - + ", indices=" + indices - + '}'; - } - - /** - * Reads the table information from the given database. - * - * @param database The database to read the information from. - * @param tableName The table name. - * @return A TableInfo containing the schema information for the provided table name. - */ - @SuppressWarnings("SameParameterValue") - public static TableInfo read(SupportSQLiteDatabase database, String tableName) { - Map columns = readColumns(database, tableName); - Set foreignKeys = readForeignKeys(database, tableName); - Set indices = readIndices(database, tableName); - return new TableInfo(tableName, columns, foreignKeys, indices); - } - - private static Set readForeignKeys(SupportSQLiteDatabase database, - String tableName) { - Set foreignKeys = new HashSet<>(); - // this seems to return everything in order but it is not documented so better be safe - Cursor cursor = database.query("PRAGMA foreign_key_list(`" + tableName + "`)"); - try { - final int idColumnIndex = cursor.getColumnIndex("id"); - final int seqColumnIndex = cursor.getColumnIndex("seq"); - final int tableColumnIndex = cursor.getColumnIndex("table"); - final int onDeleteColumnIndex = cursor.getColumnIndex("on_delete"); - final int onUpdateColumnIndex = cursor.getColumnIndex("on_update"); - - final List ordered = readForeignKeyFieldMappings(cursor); - final int count = cursor.getCount(); - for (int position = 0; position < count; position++) { - cursor.moveToPosition(position); - final int seq = cursor.getInt(seqColumnIndex); - if (seq != 0) { - continue; - } - final int id = cursor.getInt(idColumnIndex); - List myColumns = new ArrayList<>(); - List refColumns = new ArrayList<>(); - for (ForeignKeyWithSequence key : ordered) { - if (key.mId == id) { - myColumns.add(key.mFrom); - refColumns.add(key.mTo); - } - } - foreignKeys.add(new ForeignKey( - cursor.getString(tableColumnIndex), - cursor.getString(onDeleteColumnIndex), - cursor.getString(onUpdateColumnIndex), - myColumns, - refColumns - )); - } - } finally { - cursor.close(); - } - return foreignKeys; - } - - private static List readForeignKeyFieldMappings(Cursor cursor) { - final int idColumnIndex = cursor.getColumnIndex("id"); - final int seqColumnIndex = cursor.getColumnIndex("seq"); - final int fromColumnIndex = cursor.getColumnIndex("from"); - final int toColumnIndex = cursor.getColumnIndex("to"); - final int count = cursor.getCount(); - List result = new ArrayList<>(); - for (int i = 0; i < count; i++) { - cursor.moveToPosition(i); - result.add(new ForeignKeyWithSequence( - cursor.getInt(idColumnIndex), - cursor.getInt(seqColumnIndex), - cursor.getString(fromColumnIndex), - cursor.getString(toColumnIndex) - )); - } - Collections.sort(result); - return result; - } - - private static Map readColumns(SupportSQLiteDatabase database, - String tableName) { - Cursor cursor = database - .query("PRAGMA table_info(`" + tableName + "`)"); - //noinspection TryFinallyCanBeTryWithResources - Map columns = new HashMap<>(); - try { - if (cursor.getColumnCount() > 0) { - int nameIndex = cursor.getColumnIndex("name"); - int typeIndex = cursor.getColumnIndex("type"); - int notNullIndex = cursor.getColumnIndex("notnull"); - int pkIndex = cursor.getColumnIndex("pk"); - int defaultValueIndex = cursor.getColumnIndex("dflt_value"); - - while (cursor.moveToNext()) { - final String name = cursor.getString(nameIndex); - final String type = cursor.getString(typeIndex); - final boolean notNull = 0 != cursor.getInt(notNullIndex); - final int primaryKeyPosition = cursor.getInt(pkIndex); - final String defaultValue = cursor.getString(defaultValueIndex); - columns.put(name, - new Column(name, type, notNull, primaryKeyPosition, defaultValue, - CREATED_FROM_DATABASE)); - } - } - } finally { - cursor.close(); - } - return columns; - } - - /** - * @return null if we cannot read the indices due to older sqlite implementations. - */ - @Nullable - private static Set readIndices(SupportSQLiteDatabase database, String tableName) { - Cursor cursor = database.query("PRAGMA index_list(`" + tableName + "`)"); - try { - final int nameColumnIndex = cursor.getColumnIndex("name"); - final int originColumnIndex = cursor.getColumnIndex("origin"); - final int uniqueIndex = cursor.getColumnIndex("unique"); - if (nameColumnIndex == -1 || originColumnIndex == -1 || uniqueIndex == -1) { - // we cannot read them so better not validate any index. - return null; - } - HashSet indices = new HashSet<>(); - while (cursor.moveToNext()) { - String origin = cursor.getString(originColumnIndex); - if (!"c".equals(origin)) { - // Ignore auto-created indices - continue; - } - String name = cursor.getString(nameColumnIndex); - boolean unique = cursor.getInt(uniqueIndex) == 1; - Index index = readIndex(database, name, unique); - if (index == null) { - // we cannot read it properly so better not read it - return null; - } - indices.add(index); - } - return indices; - } finally { - cursor.close(); - } - } - - /** - * @return null if we cannot read the index due to older sqlite implementations. - */ - @Nullable - private static Index readIndex(SupportSQLiteDatabase database, String name, boolean unique) { - Cursor cursor = database.query("PRAGMA index_xinfo(`" + name + "`)"); - try { - final int seqnoColumnIndex = cursor.getColumnIndex("seqno"); - final int cidColumnIndex = cursor.getColumnIndex("cid"); - final int nameColumnIndex = cursor.getColumnIndex("name"); - if (seqnoColumnIndex == -1 || cidColumnIndex == -1 || nameColumnIndex == -1) { - // we cannot read them so better not validate any index. - return null; - } - final TreeMap results = new TreeMap<>(); - - while (cursor.moveToNext()) { - int cid = cursor.getInt(cidColumnIndex); - if (cid < 0) { - // Ignore SQLite row ID - continue; - } - int seq = cursor.getInt(seqnoColumnIndex); - String columnName = cursor.getString(nameColumnIndex); - results.put(seq, columnName); - } - final List columns = new ArrayList<>(results.size()); - columns.addAll(results.values()); - return new Index(name, unique, columns); - } finally { - cursor.close(); - } - } - - /** - * Holds the information about a database column. - */ - @SuppressWarnings("WeakerAccess") - public static class Column { - /** - * The column name. - */ - public final String name; - /** - * The column type affinity. - */ - public final String type; - /** - * The column type after it is normalized to one of the basic types according to - * https://www.sqlite.org/datatype3.html Section 3.1. - *

    - * This is the value Room uses for equality check. - */ - @ColumnInfo.SQLiteTypeAffinity - public final int affinity; - /** - * Whether or not the column can be NULL. - */ - public final boolean notNull; - /** - * The position of the column in the list of primary keys, 0 if the column is not part - * of the primary key. - *

    - * This information is only available in API 20+. - * (SQLite version 3.7.16.2) - * On older platforms, it will be 1 if the column is part of the primary key and 0 - * otherwise. - *

    - * The {@link #equals(Object)} implementation handles this inconsistency based on - * API levels os if you are using a custom SQLite deployment, it may return false - * positives. - */ - public final int primaryKeyPosition; - /** - * The default value of this column. - */ - public final String defaultValue; - - @CreatedFrom - private final int mCreatedFrom; - - /** - * @deprecated Use {@link Column#Column(String, String, boolean, int, String, int)} instead. - */ - @Deprecated - public Column(String name, String type, boolean notNull, int primaryKeyPosition) { - this(name, type, notNull, primaryKeyPosition, null, CREATED_FROM_UNKNOWN); - } - - // if you change this constructor, you must change TableInfoWriter.kt - public Column(String name, String type, boolean notNull, int primaryKeyPosition, - String defaultValue, @CreatedFrom int createdFrom) { - this.name = name; - this.type = type; - this.notNull = notNull; - this.primaryKeyPosition = primaryKeyPosition; - this.affinity = findAffinity(type); - this.defaultValue = defaultValue; - this.mCreatedFrom = createdFrom; - } - - /** - * Implements https://www.sqlite.org/datatype3.html section 3.1 - * - * @param type The type that was given to the sqlite - * @return The normalized type which is one of the 5 known affinities - */ - @ColumnInfo.SQLiteTypeAffinity - private static int findAffinity(@Nullable String type) { - if (type == null) { - return ColumnInfo.BLOB; - } - String uppercaseType = type.toUpperCase(Locale.US); - if (uppercaseType.contains("INT")) { - return ColumnInfo.INTEGER; - } - if (uppercaseType.contains("CHAR") - || uppercaseType.contains("CLOB") - || uppercaseType.contains("TEXT")) { - return ColumnInfo.TEXT; - } - if (uppercaseType.contains("BLOB")) { - return ColumnInfo.BLOB; - } - if (uppercaseType.contains("REAL") - || uppercaseType.contains("FLOA") - || uppercaseType.contains("DOUB")) { - return ColumnInfo.REAL; - } - // sqlite returns NUMERIC here but it is like a catch all. We already - // have UNDEFINED so it is better to use UNDEFINED for consistency. - return ColumnInfo.UNDEFINED; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Column column = (Column) o; - if (Build.VERSION.SDK_INT >= 20) { - if (primaryKeyPosition != column.primaryKeyPosition) return false; - } else { - if (isPrimaryKey() != column.isPrimaryKey()) return false; - } - - if (!name.equals(column.name)) return false; - //noinspection SimplifiableIfStatement - if (notNull != column.notNull) return false; - - // Only validate default value if it was defined in an entity, i.e. if the info - // from the compiler itself has it. b/136019383 - if (mCreatedFrom == CREATED_FROM_ENTITY - && column.mCreatedFrom == CREATED_FROM_DATABASE - && (defaultValue != null && !defaultValue.equals(column.defaultValue))) { - return false; - } else if (mCreatedFrom == CREATED_FROM_DATABASE - && column.mCreatedFrom == CREATED_FROM_ENTITY - && (column.defaultValue != null && !column.defaultValue.equals(defaultValue))) { - return false; - } else if (mCreatedFrom != CREATED_FROM_UNKNOWN - && mCreatedFrom == column.mCreatedFrom - && (defaultValue != null ? !defaultValue.equals(column.defaultValue) - : column.defaultValue != null)) { - return false; - } - - return affinity == column.affinity; - } - - /** - * Returns whether this column is part of the primary key or not. - * - * @return True if this column is part of the primary key, false otherwise. - */ - public boolean isPrimaryKey() { - return primaryKeyPosition > 0; - } - - @Override - public int hashCode() { - int result = name.hashCode(); - result = 31 * result + affinity; - result = 31 * result + (notNull ? 1231 : 1237); - result = 31 * result + primaryKeyPosition; - // Default value is not part of the hashcode since we conditionally check it for - // equality which would break the equals + hashcode contract. - // result = 31 * result + (defaultValue != null ? defaultValue.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "Column{" - + "name='" + name + '\'' - + ", type='" + type + '\'' - + ", affinity='" + affinity + '\'' - + ", notNull=" + notNull - + ", primaryKeyPosition=" + primaryKeyPosition - + ", defaultValue='" + defaultValue + '\'' - + '}'; - } - } - - /** - * Holds the information about an SQLite foreign key - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public static class ForeignKey { - @NonNull - public final String referenceTable; - @NonNull - public final String onDelete; - @NonNull - public final String onUpdate; - @NonNull - public final List columnNames; - @NonNull - public final List referenceColumnNames; - - public ForeignKey(@NonNull String referenceTable, @NonNull String onDelete, - @NonNull String onUpdate, - @NonNull List columnNames, @NonNull List referenceColumnNames) { - this.referenceTable = referenceTable; - this.onDelete = onDelete; - this.onUpdate = onUpdate; - this.columnNames = Collections.unmodifiableList(columnNames); - this.referenceColumnNames = Collections.unmodifiableList(referenceColumnNames); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ForeignKey that = (ForeignKey) o; - - if (!referenceTable.equals(that.referenceTable)) return false; - if (!onDelete.equals(that.onDelete)) return false; - if (!onUpdate.equals(that.onUpdate)) return false; - //noinspection SimplifiableIfStatement - if (!columnNames.equals(that.columnNames)) return false; - return referenceColumnNames.equals(that.referenceColumnNames); - } - - @Override - public int hashCode() { - int result = referenceTable.hashCode(); - result = 31 * result + onDelete.hashCode(); - result = 31 * result + onUpdate.hashCode(); - result = 31 * result + columnNames.hashCode(); - result = 31 * result + referenceColumnNames.hashCode(); - return result; - } - - @Override - public String toString() { - return "ForeignKey{" - + "referenceTable='" + referenceTable + '\'' - + ", onDelete='" + onDelete + '\'' - + ", onUpdate='" + onUpdate + '\'' - + ", columnNames=" + columnNames - + ", referenceColumnNames=" + referenceColumnNames - + '}'; - } - } - - /** - * Temporary data holder for a foreign key row in the pragma result. We need this to ensure - * sorting in the generated foreign key object. - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - static class ForeignKeyWithSequence implements Comparable { - final int mId; - final int mSequence; - final String mFrom; - final String mTo; - - ForeignKeyWithSequence(int id, int sequence, String from, String to) { - mId = id; - mSequence = sequence; - mFrom = from; - mTo = to; - } - - @Override - public int compareTo(@NonNull ForeignKeyWithSequence o) { - final int idCmp = mId - o.mId; - if (idCmp == 0) { - return mSequence - o.mSequence; - } else { - return idCmp; - } - } - } - - /** - * Holds the information about an SQLite index - * - * @hide - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - public static class Index { - // should match the value in Index.kt - public static final String DEFAULT_PREFIX = "index_"; - public final String name; - public final boolean unique; - public final List columns; - - public Index(String name, boolean unique, List columns) { - this.name = name; - this.unique = unique; - this.columns = columns; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Index index = (Index) o; - if (unique != index.unique) { - return false; - } - if (!columns.equals(index.columns)) { - return false; - } - if (name.startsWith(Index.DEFAULT_PREFIX)) { - return index.name.startsWith(Index.DEFAULT_PREFIX); - } else { - return name.equals(index.name); - } - } - - @Override - public int hashCode() { - int result; - if (name.startsWith(DEFAULT_PREFIX)) { - result = DEFAULT_PREFIX.hashCode(); - } else { - result = name.hashCode(); - } - result = 31 * result + (unique ? 1 : 0); - result = 31 * result + columns.hashCode(); - return result; - } - - @Override - public String toString() { - return "Index{" - + "name='" + name + '\'' - + ", unique=" + unique - + ", columns=" + columns - + '}'; - } - } -} diff --git a/app/src/main/java/androidx/room/util/ViewInfo.java b/app/src/main/java/androidx/room/util/ViewInfo.java deleted file mode 100644 index 5e4b9b2bbe..0000000000 --- a/app/src/main/java/androidx/room/util/ViewInfo.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.room.util; - -import android.database.Cursor; - -import androidx.annotation.RestrictTo; -import androidx.sqlite.db.SupportSQLiteDatabase; - -/** - * A data class that holds the information about a view. - *

    - * This derives information from sqlite_master. - *

    - * Even though SQLite column names are case insensitive, this class uses case sensitive matching. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -public class ViewInfo { - - /** - * The view name - */ - public final String name; - - /** - * The SQL of CREATE VIEW. - */ - public final String sql; - - public ViewInfo(String name, String sql) { - this.name = name; - this.sql = sql; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ViewInfo viewInfo = (ViewInfo) o; - return (name != null ? name.equals(viewInfo.name) : viewInfo.name == null) - && (sql != null ? sql.equals(viewInfo.sql) : viewInfo.sql == null); - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (sql != null ? sql.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "ViewInfo{" - + "name='" + name + '\'' - + ", sql='" + sql + '\'' - + '}'; - } - - /** - * Reads the view information from the given database. - * - * @param database The database to read the information from. - * @param viewName The view name. - * @return A ViewInfo containing the schema information for the provided view name. - */ - @SuppressWarnings("SameParameterValue") - public static ViewInfo read(SupportSQLiteDatabase database, String viewName) { - Cursor cursor = database.query("SELECT name, sql FROM sqlite_master " - + "WHERE type = 'view' AND name = '" + viewName + "'"); - //noinspection TryFinallyCanBeTryWithResources - try { - if (cursor.moveToFirst()) { - return new ViewInfo(cursor.getString(0), cursor.getString(1)); - } else { - return new ViewInfo(viewName, null); - } - } finally { - cursor.close(); - } - } -}