Add DefaultContentMetadata serialization and deserialization

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=188889091
This commit is contained in:
eguven 2018-03-13 10:12:38 -07:00 committed by Oliver Woodman
parent 18b1ec7a2f
commit e565a46a7d
2 changed files with 109 additions and 34 deletions

View File

@ -16,20 +16,50 @@
package com.google.android.exoplayer2.upstream.cache; package com.google.android.exoplayer2.upstream.cache;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
/** Default implementation of {@link ContentMetadata}. Values are stored as byte arrays. */ /** Default implementation of {@link ContentMetadata}. Values are stored as byte arrays. */
public final class DefaultContentMetadata implements ContentMetadata { public final class DefaultContentMetadata implements ContentMetadata {
private static final int MAX_VALUE_LENGTH = 10 * 1024 * 1024;
/**
* Deserializes a {@link DefaultContentMetadata} from the given input stream.
*
* @param input Input stream to read from.
* @return a {@link DefaultContentMetadata} instance.
* @throws IOException If an error occurs during reading from input.
*/
public static DefaultContentMetadata readFromStream(DataInputStream input) throws IOException {
int size = input.readInt();
HashMap<String, byte[]> metadata = new HashMap<>();
for (int i = 0; i < size; i++) {
String name = input.readUTF();
int valueSize = input.readInt();
if (valueSize < 0 || valueSize > MAX_VALUE_LENGTH) {
throw new IOException("Invalid value size: " + valueSize);
}
byte[] value = new byte[valueSize];
input.readFully(value);
metadata.put(name, value);
}
return new DefaultContentMetadata(metadata);
}
private final Map<String, byte[]> metadata; private final Map<String, byte[]> metadata;
/** Constructs an empty {@link DefaultContentMetadata}. */ /** Constructs an empty {@link DefaultContentMetadata}. */
public DefaultContentMetadata() { public DefaultContentMetadata() {
this.metadata = new HashMap<>(); this(Collections.<String, byte[]>emptyMap());
} }
/** /**
@ -37,53 +67,66 @@ public final class DefaultContentMetadata implements ContentMetadata {
* applying {@code mutations}. * applying {@code mutations}.
*/ */
public DefaultContentMetadata(DefaultContentMetadata other, ContentMetadataMutations mutations) { public DefaultContentMetadata(DefaultContentMetadata other, ContentMetadataMutations mutations) {
this.metadata = new HashMap<>(other.metadata); this(applyMutations(other.metadata, mutations));
applyMutations(mutations); }
private DefaultContentMetadata(Map<String, byte[]> metadata) {
this.metadata = Collections.unmodifiableMap(metadata);
}
/**
* Serializes itself to a {@link DataOutputStream}.
*
* @param output Output stream to store the values.
* @throws IOException If an error occurs during writing values to output.
*/
public void writeToStream(DataOutputStream output) throws IOException {
output.writeInt(metadata.size());
for (Entry<String, byte[]> entry : metadata.entrySet()) {
output.writeUTF(entry.getKey());
byte[] value = entry.getValue();
output.writeInt(value.length);
output.write(value);
}
} }
@Override @Override
public final byte[] get(String name, byte[] defaultValue) { public final byte[] get(String name, byte[] defaultValue) {
synchronized (metadata) { if (metadata.containsKey(name)) {
if (metadata.containsKey(name)) { return metadata.get(name);
return metadata.get(name); } else {
} else { return defaultValue;
return defaultValue;
}
} }
} }
@Override @Override
public final String get(String name, String defaultValue) { public final String get(String name, String defaultValue) {
synchronized (metadata) { if (metadata.containsKey(name)) {
if (metadata.containsKey(name)) { byte[] bytes = metadata.get(name);
byte[] bytes = metadata.get(name); return new String(bytes, Charset.forName(C.UTF8_NAME));
return new String(bytes, Charset.forName(C.UTF8_NAME)); } else {
} else { return defaultValue;
return defaultValue;
}
} }
} }
@Override @Override
public final long get(String name, long defaultValue) { public final long get(String name, long defaultValue) {
synchronized (metadata) { if (metadata.containsKey(name)) {
if (metadata.containsKey(name)) { byte[] bytes = metadata.get(name);
byte[] bytes = metadata.get(name); return ByteBuffer.wrap(bytes).getLong();
return ByteBuffer.wrap(bytes).getLong(); } else {
} else { return defaultValue;
return defaultValue;
}
} }
} }
@Override @Override
public final boolean contains(String name) { public final boolean contains(String name) {
synchronized (metadata) { return metadata.containsKey(name);
return metadata.containsKey(name);
}
} }
private void applyMutations(ContentMetadataMutations mutations) { private static Map<String, byte[]> applyMutations(
Map<String, byte[]> otherMetadata, ContentMetadataMutations mutations) {
HashMap<String, byte[]> metadata = new HashMap<>(otherMetadata);
List<String> removedValues = mutations.getRemovedValues(); List<String> removedValues = mutations.getRemovedValues();
for (int i = 0; i < removedValues.size(); i++) { for (int i = 0; i < removedValues.size(); i++) {
metadata.remove(removedValues.get(i)); metadata.remove(removedValues.get(i));
@ -91,8 +134,16 @@ public final class DefaultContentMetadata implements ContentMetadata {
Map<String, Object> editedValues = mutations.getEditedValues(); Map<String, Object> editedValues = mutations.getEditedValues();
for (String name : editedValues.keySet()) { for (String name : editedValues.keySet()) {
Object value = editedValues.get(name); Object value = editedValues.get(name);
metadata.put(name, getBytes(value)); byte[] bytes = getBytes(value);
if (bytes.length > MAX_VALUE_LENGTH) {
throw new IllegalArgumentException(
String.format(
"The size of %s (%d) is greater than maximum allowed: %d",
name, bytes.length, MAX_VALUE_LENGTH));
}
metadata.put(name, bytes);
} }
return metadata;
} }
private static byte[] getBytes(Object value) { private static byte[] getBytes(Object value) {

View File

@ -17,6 +17,10 @@ package com.google.android.exoplayer2.upstream.cache;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -30,7 +34,7 @@ public class DefaultContentMetadataTest {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
contentMetadata = createAbstractContentMetadata(); contentMetadata = createContentMetadata();
} }
@Test @Test
@ -40,7 +44,7 @@ public class DefaultContentMetadataTest {
@Test @Test
public void testContainsReturnsTrueForInitialValue() throws Exception { public void testContainsReturnsTrueForInitialValue() throws Exception {
contentMetadata = createAbstractContentMetadata("metadata name", "value"); contentMetadata = createContentMetadata("metadata name", "value");
assertThat(contentMetadata.contains("metadata name")).isTrue(); assertThat(contentMetadata.contains("metadata name")).isTrue();
} }
@ -51,7 +55,7 @@ public class DefaultContentMetadataTest {
@Test @Test
public void testGetReturnsInitialValue() throws Exception { public void testGetReturnsInitialValue() throws Exception {
contentMetadata = createAbstractContentMetadata("metadata name", "value"); contentMetadata = createContentMetadata("metadata name", "value");
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value"); assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
} }
@ -95,7 +99,7 @@ public class DefaultContentMetadataTest {
@Test @Test
public void testEditMetadata() throws Exception { public void testEditMetadata() throws Exception {
contentMetadata = createAbstractContentMetadata("metadata name", "value"); contentMetadata = createContentMetadata("metadata name", "value");
ContentMetadataMutations mutations = new ContentMetadataMutations(); ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", "edited value"); mutations.set("metadata name", "edited value");
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations); contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
@ -104,7 +108,7 @@ public class DefaultContentMetadataTest {
@Test @Test
public void testRemoveMetadata() throws Exception { public void testRemoveMetadata() throws Exception {
contentMetadata = createAbstractContentMetadata("metadata name", "value"); contentMetadata = createContentMetadata("metadata name", "value");
ContentMetadataMutations mutations = new ContentMetadataMutations(); ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.remove("metadata name"); mutations.remove("metadata name");
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations); contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
@ -129,7 +133,27 @@ public class DefaultContentMetadataTest {
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value"); assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
} }
private DefaultContentMetadata createAbstractContentMetadata(String... pairs) { @Test
public void testSerializeDeserialize() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata1 name", "value");
mutations.set("metadata2 name", 12345);
byte[] metadata3 = {1, 2, 3};
mutations.set("metadata3 name", metadata3);
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
contentMetadata.writeToStream(new DataOutputStream(outputStream));
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
DefaultContentMetadata contentMetadata2 =
DefaultContentMetadata.readFromStream(new DataInputStream(inputStream));
assertThat(contentMetadata2.get("metadata1 name", "default value")).isEqualTo("value");
assertThat(contentMetadata2.get("metadata2 name", 0)).isEqualTo(12345);
assertThat(contentMetadata2.get("metadata3 name", new byte[] {})).isEqualTo(metadata3);
}
private DefaultContentMetadata createContentMetadata(String... pairs) {
assertThat(pairs.length % 2).isEqualTo(0); assertThat(pairs.length % 2).isEqualTo(0);
ContentMetadataMutations mutations = new ContentMetadataMutations(); ContentMetadataMutations mutations = new ContentMetadataMutations();
for (int i = 0; i < pairs.length; i += 2) { for (int i = 0; i < pairs.length; i += 2) {