1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.sqlite.inspection; 18 19 import android.database.sqlite.SQLiteDatabase; 20 21 import org.jspecify.annotations.NonNull; 22 23 import java.io.File; 24 import java.util.Objects; 25 26 final class DatabaseExtensions { 27 private static final String sInMemoryDatabasePath = ":memory:"; 28 29 /** Placeholder {@code %x} is for database's hashcode */ 30 private static final String sInMemoryDatabaseNameFormat = 31 sInMemoryDatabasePath + " {hashcode=0x%x}"; 32 DatabaseExtensions()33 private DatabaseExtensions() { } 34 35 /** Thread-safe as {@link SQLiteDatabase#getPath} and {@link Object#hashCode) are thread-safe.*/ pathForDatabase(@onNull SQLiteDatabase database)36 static String pathForDatabase(@NonNull SQLiteDatabase database) { 37 return isInMemoryDatabase(database) 38 ? String.format(sInMemoryDatabaseNameFormat, database.hashCode()) 39 : new File(database.getPath()).getAbsolutePath(); 40 } 41 42 /** Thread-safe as {@link SQLiteDatabase#getPath} is thread-safe. */ isInMemoryDatabase(@onNull SQLiteDatabase database)43 static boolean isInMemoryDatabase(@NonNull SQLiteDatabase database) { 44 return Objects.equals(sInMemoryDatabasePath, database.getPath()); 45 } 46 47 /** 48 * Attempts to call {@link SQLiteDatabase#acquireReference} on the provided object. 49 * 50 * @return true if the operation was successful; false if unsuccessful because the database 51 * was already closed; otherwise re-throws the exception thrown by 52 * {@link SQLiteDatabase#acquireReference}. 53 */ tryAcquireReference(@onNull SQLiteDatabase database)54 static boolean tryAcquireReference(@NonNull SQLiteDatabase database) { 55 if (!database.isOpen()) { 56 return false; 57 } 58 59 try { 60 database.acquireReference(); 61 return true; // success 62 } catch (IllegalStateException e) { 63 if (isAttemptAtUsingClosedDatabase(e)) { 64 return false; 65 } 66 throw e; 67 } 68 } 69 70 /** 71 * Note that this is best-effort as relies on Exception message parsing, which could break in 72 * the future. 73 * Use in the context where false negatives (more likely) and false positives (less likely 74 * due to the specificity of the message) are tolerable, e.g. to assign error codes where if 75 * it fails we will just send an 'unknown' error. 76 */ isAttemptAtUsingClosedDatabase(IllegalStateException exception)77 static boolean isAttemptAtUsingClosedDatabase(IllegalStateException exception) { 78 String message = exception.getMessage(); 79 return message != null && (message.contains("attempt to re-open an already-closed object") 80 || message.contains( 81 "Cannot perform this operation because the connection pool has been closed")); 82 } 83 } 84