summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYan Yan <evitayan@google.com>2020-11-06 21:43:58 -0800
committerYan Yan <evitayan@google.com>2020-11-13 14:49:29 -0800
commit296c895e10cf835e3158e0327e4f202559323f3e (patch)
treee817c4a4987dfb5c6803cca245311c0040ce9b55
parentb92cbc787e0420b12619540083fee850775ce3c7 (diff)
Add utils for converting Maps and ParcelUuid to/from PersistableBundle
This commit expands the PersistableBundleUtils by adding maps. LinkedHashMap is used in an attempt to preserve ordering where stability is important. Similarly, this commit adds the ability to persist ParcelUuid(s) via conversion to Strings. This commit also adds a helper method to safely read and write to and from disk Bug: 163611304 Test: New tests added, passing Change-Id: Ife24e94006445007be68ab0e03f27b2fd5643aa2
-rw-r--r--services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java186
-rw-r--r--tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java63
2 files changed, 245 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
index 73054ce04623..7d276c797676 100644
--- a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
+++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java
@@ -17,15 +17,31 @@
package com.android.server.vcn.util;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelUuid;
import android.os.PersistableBundle;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
/** @hide */
public class PersistableBundleUtils {
private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d";
- private static final String LIST_LENGTH_KEY = "LIST_LENGTH";
+ private static final String COLLECTION_SIZE_KEY = "COLLECTION_LENGTH";
+ private static final String MAP_KEY_FORMAT = "MAP_KEY_%d";
+ private static final String MAP_VALUE_FORMAT = "MAP_VALUE_%d";
+
+ private static final String PARCEL_UUID_KEY = "PARCEL_UUID";
/**
* Functional interface to convert an object of the specified type to a PersistableBundle.
@@ -57,6 +73,33 @@ public class PersistableBundleUtils {
}
/**
+ * Converts a ParcelUuid to a PersistableBundle.
+ *
+ * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
+ * PersistableBundle object.
+ *
+ * @param uuid a ParcelUuid instance to persist
+ * @return the PersistableBundle instance
+ */
+ public static PersistableBundle fromParcelUuid(ParcelUuid uuid) {
+ final PersistableBundle result = new PersistableBundle();
+
+ result.putString(PARCEL_UUID_KEY, uuid.toString());
+
+ return result;
+ }
+
+ /**
+ * Converts from a PersistableBundle to a ParcelUuid.
+ *
+ * @param bundle the PersistableBundle containing the ParcelUuid
+ * @return the ParcelUuid instance
+ */
+ public static ParcelUuid toParcelUuid(PersistableBundle bundle) {
+ return ParcelUuid.fromString(bundle.getString(PARCEL_UUID_KEY));
+ }
+
+ /**
* Converts from a list of Persistable objects to a single PersistableBundle.
*
* <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
@@ -72,7 +115,7 @@ public class PersistableBundleUtils {
@NonNull List<T> in, @NonNull Serializer<T> serializer) {
final PersistableBundle result = new PersistableBundle();
- result.putInt(LIST_LENGTH_KEY, in.size());
+ result.putInt(COLLECTION_SIZE_KEY, in.size());
for (int i = 0; i < in.size(); i++) {
final String key = String.format(LIST_KEY_FORMAT, i);
result.putPersistableBundle(key, serializer.toPersistableBundle(in.get(i)));
@@ -91,7 +134,7 @@ public class PersistableBundleUtils {
@NonNull
public static <T> List<T> toList(
@NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) {
- final int listLength = in.getInt(LIST_LENGTH_KEY);
+ final int listLength = in.getInt(COLLECTION_SIZE_KEY);
final ArrayList<T> result = new ArrayList<>(listLength);
for (int i = 0; i < listLength; i++) {
@@ -102,4 +145,141 @@ public class PersistableBundleUtils {
}
return result;
}
+
+ /**
+ * Converts from a Map of Persistable objects to a single PersistableBundle.
+ *
+ * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned
+ * PersistableBundle object.
+ *
+ * @param <K> the type of the map-key to convert to the PersistableBundle
+ * @param <V> the type of the map-value to convert to the PersistableBundle
+ * @param in the Map of objects implementing the {@link Persistable} interface
+ * @param keySerializer an implementation of the {@link Serializer} functional interface that
+ * converts a map-key of type T to a PersistableBundle
+ * @param valueSerializer an implementation of the {@link Serializer} functional interface that
+ * converts a map-value of type E to a PersistableBundle
+ */
+ @NonNull
+ public static <K, V> PersistableBundle fromMap(
+ @NonNull Map<K, V> in,
+ @NonNull Serializer<K> keySerializer,
+ @NonNull Serializer<V> valueSerializer) {
+ final PersistableBundle result = new PersistableBundle();
+
+ result.putInt(COLLECTION_SIZE_KEY, in.size());
+ int i = 0;
+ for (Entry<K, V> entry : in.entrySet()) {
+ final String keyKey = String.format(MAP_KEY_FORMAT, i);
+ final String valueKey = String.format(MAP_VALUE_FORMAT, i);
+ result.putPersistableBundle(keyKey, keySerializer.toPersistableBundle(entry.getKey()));
+ result.putPersistableBundle(
+ valueKey, valueSerializer.toPersistableBundle(entry.getValue()));
+
+ i++;
+ }
+
+ return result;
+ }
+
+ /**
+ * Converts from a PersistableBundle to a Map of objects.
+ *
+ * <p>In an attempt to preserve ordering, the returned map will be a LinkedHashMap. However, the
+ * guarantees on the ordering can only ever be as strong as the map that was serialized in
+ * {@link fromMap()}. If the initial map that was serialized had no ordering guarantees, the
+ * deserialized map similarly may be of a non-deterministic order.
+ *
+ * @param <K> the type of the map-key to convert from a PersistableBundle
+ * @param <V> the type of the map-value to convert from a PersistableBundle
+ * @param in the PersistableBundle containing the persisted Map
+ * @param keyDeserializer an implementation of the {@link Deserializer} functional interface
+ * that builds the relevant type of map-key.
+ * @param valueDeserializer an implementation of the {@link Deserializer} functional interface
+ * that builds the relevant type of map-value.
+ * @return An instance of the parsed map as a LinkedHashMap (in an attempt to preserve
+ * ordering).
+ */
+ @NonNull
+ public static <K, V> LinkedHashMap<K, V> toMap(
+ @NonNull PersistableBundle in,
+ @NonNull Deserializer<K> keyDeserializer,
+ @NonNull Deserializer<V> valueDeserializer) {
+ final int mapSize = in.getInt(COLLECTION_SIZE_KEY);
+ final LinkedHashMap<K, V> result = new LinkedHashMap<>(mapSize);
+
+ for (int i = 0; i < mapSize; i++) {
+ final String keyKey = String.format(MAP_KEY_FORMAT, i);
+ final String valueKey = String.format(MAP_VALUE_FORMAT, i);
+ final PersistableBundle keyBundle = in.getPersistableBundle(keyKey);
+ final PersistableBundle valueBundle = in.getPersistableBundle(valueKey);
+
+ final K key = keyDeserializer.fromPersistableBundle(keyBundle);
+ final V value = valueDeserializer.fromPersistableBundle(valueBundle);
+ result.put(key, value);
+ }
+ return result;
+ }
+
+ /**
+ * Ensures safe reading and writing of {@link PersistableBundle}s to and from disk.
+ *
+ * <p>This class will enforce exclusion between reads and writes using the standard semantics of
+ * a ReadWriteLock. Specifically, concurrent readers ARE allowed, but reads/writes from/to the
+ * file are mutually exclusive. In other words, for an unbounded number n, the acceptable states
+ * are n readers, OR 1 writer (but not both).
+ */
+ public static class LockingReadWriteHelper {
+ private final ReadWriteLock mDiskLock = new ReentrantReadWriteLock();
+ private final String mPath;
+
+ public LockingReadWriteHelper(@NonNull String path) {
+ mPath = Objects.requireNonNull(path, "fileName was null");
+ }
+
+ /**
+ * Reads the {@link PersistableBundle} from the disk.
+ *
+ * @return the PersistableBundle, if the file existed, or null otherwise
+ */
+ @Nullable
+ public PersistableBundle readFromDisk() throws IOException {
+ try {
+ mDiskLock.readLock().lock();
+ final File file = new File(mPath);
+ if (!file.exists()) {
+ return null;
+ }
+
+ try (FileInputStream fis = new FileInputStream(file)) {
+ return PersistableBundle.readFromStream(fis);
+ }
+ } finally {
+ mDiskLock.readLock().unlock();
+ }
+ }
+
+ /**
+ * Writes a {@link PersistableBundle} to disk.
+ *
+ * @param bundle the {@link PersistableBundle} to write to disk
+ */
+ public void writeToDisk(@NonNull PersistableBundle bundle) throws IOException {
+ Objects.requireNonNull(bundle, "bundle was null");
+
+ try {
+ mDiskLock.writeLock().lock();
+ final File file = new File(mPath);
+ if (!file.exists()) {
+ file.getParentFile().mkdirs();
+ }
+
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ bundle.writeToStream(fos);
+ }
+ } finally {
+ mDiskLock.writeLock().unlock();
+ }
+ }
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
index f78eeb69b5eb..07d3049ab6ed 100644
--- a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
+++ b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java
@@ -28,6 +28,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
@@ -40,6 +41,43 @@ public class PersistableBundleUtilsTest {
private static final int NUM_COLLECTION_ENTRIES = 10;
+ private static class TestKey {
+ private static final String TEST_INTEGER_KEY =
+ "mTestInteger"; // Purposely colliding with keys of test class to ensure namespacing
+ private final int mTestInteger;
+
+ TestKey(int testInteger) {
+ mTestInteger = testInteger;
+ }
+
+ TestKey(PersistableBundle in) {
+ mTestInteger = in.getInt(TEST_INTEGER_KEY);
+ }
+
+ public PersistableBundle toPersistableBundle() {
+ final PersistableBundle result = new PersistableBundle();
+
+ result.putInt(TEST_INTEGER_KEY, mTestInteger);
+
+ return result;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTestInteger);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof TestKey)) {
+ return false;
+ }
+
+ final TestKey other = (TestKey) o;
+ return mTestInteger == other.mTestInteger;
+ }
+ }
+
private static class TestClass {
private static final String TEST_INTEGER_KEY = "mTestInteger";
private final int mTestInteger;
@@ -113,7 +151,7 @@ public class PersistableBundleUtilsTest {
}
@Test
- public void testConversionLossless() throws Exception {
+ public void testListConversionLossless() throws Exception {
final List<TestClass> sourceList = new ArrayList<>();
for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) {
final PersistableBundle innerBundle = new PersistableBundle();
@@ -128,4 +166,27 @@ public class PersistableBundleUtilsTest {
assertEquals(sourceList, resultList);
}
+
+ @Test
+ public void testMapConversionLossless() throws Exception {
+ final LinkedHashMap<TestKey, TestClass> sourceMap = new LinkedHashMap<>();
+ for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) {
+ final TestKey key = new TestKey(i * i);
+
+ final PersistableBundle innerBundle = new PersistableBundle();
+ innerBundle.putInt(TEST_KEY, i);
+ final TestClass value =
+ new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle);
+
+ sourceMap.put(key, value);
+ }
+
+ final PersistableBundle bundled =
+ PersistableBundleUtils.fromMap(
+ sourceMap, TestKey::toPersistableBundle, TestClass::toPersistableBundle);
+ final LinkedHashMap<TestKey, TestClass> resultList =
+ PersistableBundleUtils.toMap(bundled, TestKey::new, TestClass::new);
+
+ assertEquals(sourceMap, resultList);
+ }
}