/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.modules.storage; import javax.annotation.Nullable; import java.util.Arrays; import java.util.Iterator; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.text.TextUtils; import com.facebook.react.bridge.ReadableArray; import org.json.JSONException; import org.json.JSONObject; import static com.facebook.react.modules.storage.ReactDatabaseSupplier.KEY_COLUMN; import static com.facebook.react.modules.storage.ReactDatabaseSupplier.TABLE_CATALYST; import static com.facebook.react.modules.storage.ReactDatabaseSupplier.VALUE_COLUMN; /** * Helper for database operations. */ public class AsyncLocalStorageUtil { /** * Build the String required for an SQL select statement: * WHERE key IN (?, ?, ..., ?) * without 'WHERE' and with selectionCount '?' */ /* package */ static String buildKeySelection(int selectionCount) { String[] list = new String[selectionCount]; Arrays.fill(list, "?"); return KEY_COLUMN + " IN (" + TextUtils.join(", ", list) + ")"; } /** * Build the String[] arguments needed for an SQL selection, i.e.: * {a, b, c} * to be used in the SQL select statement: WHERE key in (?, ?, ?) */ /* package */ static String[] buildKeySelectionArgs(ReadableArray keys, int start, int count) { String[] selectionArgs = new String[count]; for (int keyIndex = 0; keyIndex < count; keyIndex++) { selectionArgs[keyIndex] = keys.getString(start + keyIndex); } return selectionArgs; } /** * Returns the value of the given key, or null if not found. */ public static @Nullable String getItemImpl(SQLiteDatabase db, String key) { String[] columns = {VALUE_COLUMN}; String[] selectionArgs = {key}; Cursor cursor = db.query( TABLE_CATALYST, columns, KEY_COLUMN + "=?", selectionArgs, null, null, null); try { if (!cursor.moveToFirst()) { return null; } else { return cursor.getString(0); } } finally { cursor.close(); } } /** * Sets the value for the key given, returns true if successful, false otherwise. */ /* package */ static boolean setItemImpl(SQLiteDatabase db, String key, String value) { ContentValues contentValues = new ContentValues(); contentValues.put(KEY_COLUMN, key); contentValues.put(VALUE_COLUMN, value); long inserted = db.insertWithOnConflict( TABLE_CATALYST, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE); return (-1 != inserted); } /** * Does the actual merge of the (key, value) pair with the value stored in the database. * NB: This assumes that a database lock is already in effect! * @return the errorCode of the operation */ /* package */ static boolean mergeImpl(SQLiteDatabase db, String key, String value) throws JSONException { String oldValue = getItemImpl(db, key); String newValue; if (oldValue == null) { newValue = value; } else { JSONObject oldJSON = new JSONObject(oldValue); JSONObject newJSON = new JSONObject(value); deepMergeInto(oldJSON, newJSON); newValue = oldJSON.toString(); } return setItemImpl(db, key, newValue); } /** * Merges two {@link JSONObject}s. The newJSON object will be merged with the oldJSON object by * either overriding its values, or merging them (if the values of the same key in both objects * are of type {@link JSONObject}). oldJSON will contain the result of this merge. */ private static void deepMergeInto(JSONObject oldJSON, JSONObject newJSON) throws JSONException { Iterator<?> keys = newJSON.keys(); while (keys.hasNext()) { String key = (String) keys.next(); JSONObject newJSONObject = newJSON.optJSONObject(key); JSONObject oldJSONObject = oldJSON.optJSONObject(key); if (newJSONObject != null && oldJSONObject != null) { deepMergeInto(oldJSONObject, newJSONObject); oldJSON.put(key, oldJSONObject); } else { oldJSON.put(key, newJSON.get(key)); } } } }