Add ContentMetadata and AbstractContentMetadata

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=188041418
This commit is contained in:
eguven 2018-03-06 10:23:38 -08:00 committed by Oliver Woodman
parent 73048f18c1
commit f81dc44112
3 changed files with 443 additions and 0 deletions

View File

@ -0,0 +1,151 @@
/*
* 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 com.google.android.exoplayer2.upstream.cache;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
import com.google.android.exoplayer2.util.Assertions;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/** Abstract implementation of {@link ContentMetadata}. Values are stored as byte arrays. */
public abstract class AbstractContentMetadata implements ContentMetadata {
private final Map<String, byte[]> metadata;
protected AbstractContentMetadata() {
this.metadata = new HashMap<>();
}
/** @param metadata Initial name value pairs. */
protected AbstractContentMetadata(Map<String, byte[]> metadata) {
this.metadata = new HashMap<>(metadata);
}
@Override
public final Editor edit() {
return new EditorImpl();
}
@Override
public final byte[] get(String name, byte[] defaultValue) {
synchronized (metadata) {
if (metadata.containsKey(name)) {
return metadata.get(name);
} else {
return defaultValue;
}
}
}
@Override
public final String get(String name, String defaultValue) {
synchronized (metadata) {
if (metadata.containsKey(name)) {
byte[] bytes = metadata.get(name);
return new String(bytes, Charset.forName(C.UTF8_NAME));
} else {
return defaultValue;
}
}
}
@Override
public final long get(String name, long defaultValue) {
synchronized (metadata) {
if (metadata.containsKey(name)) {
byte[] bytes = metadata.get(name);
return ByteBuffer.wrap(bytes).getLong();
} else {
return defaultValue;
}
}
}
@Override
public final boolean contains(String name) {
synchronized (metadata) {
return metadata.containsKey(name);
}
}
/**
* Called when any metadata value is changed or removed. {@code metadataValues} shouldn't be
* accessed out of this method.
*
* @param metadataValues All metadata name, value pairs.
*/
protected abstract void onChange(Map<String, byte[]> metadataValues) throws CacheException;
private void apply(ArrayList<String> removedValues, Map<String, byte[]> editedValues)
throws CacheException {
synchronized (metadata) {
for (int i = 0; i < removedValues.size(); i++) {
metadata.remove(removedValues.get(i));
}
metadata.putAll(editedValues);
onChange(Collections.unmodifiableMap(metadata));
}
}
private class EditorImpl implements Editor {
private final Map<String, byte[]> editedValues;
private final ArrayList<String> removedValues;
private EditorImpl() {
editedValues = new HashMap<>();
removedValues = new ArrayList<>();
}
@Override
public Editor set(String name, String value) {
set(name, value.getBytes());
return this;
}
@Override
public Editor set(String name, long value) {
set(name, ByteBuffer.allocate(8).putLong(value).array());
return this;
}
@Override
public Editor set(String name, byte[] value) {
editedValues.put(name, Assertions.checkNotNull(value));
removedValues.remove(name);
return this;
}
@Override
public Editor remove(String name) {
removedValues.add(name);
editedValues.remove(name);
return this;
}
@Override
public void commit() throws CacheException {
apply(removedValues, editedValues);
removedValues.clear();
editedValues.clear();
}
}
}

View File

@ -0,0 +1,104 @@
/*
* 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 com.google.android.exoplayer2.upstream.cache;
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
/** Interface for accessing cached content metadata which is stored as name, value pairs. */
public interface ContentMetadata {
/**
* Interface for modifying values in a {@link ContentMetadata} object. The changes you make in an
* editor are not copied back to the original {@link ContentMetadata} until you call {@link
* #commit()}.
*/
interface Editor {
/**
* Sets a metadata value, to be committed once {@link #commit()} is called. Passing {@code null}
* as {@code value} isn't allowed. {@code value} byte array shouldn't be modified after passed
* to this method.
*
* @param name The name of the metadata value.
* @param value The value to be set.
* @return This Editor instance, for convenience.
*/
Editor set(String name, byte[] value);
/**
* Sets a metadata value, to be committed once {@link #commit()} is called. Passing {@code null}
* as value isn't allowed.
*
* @param name The name of the metadata value.
* @param value The value to be set.
* @return This Editor instance, for convenience.
*/
Editor set(String name, String value);
/**
* Sets a metadata value, to be committed once {@link #commit()} is called.
*
* @param name The name of the metadata value.
* @param value The value to be set.
* @return This Editor instance, for convenience.
*/
Editor set(String name, long value);
/**
* Sets a metadata value, to be committed once {@link #commit()} is called. Passing {@code null}
* as value isn't allowed.
*
* @param name The name of the metadata value.
* @return This Editor instance, for convenience.
*/
Editor remove(String name);
/**
* Commits changes. It can be called only once.
*
* @throws CacheException If the commit fails.
*/
void commit() throws CacheException;
}
/** Returns an editor to change metadata values. */
Editor edit();
/**
* Returns a metadata value.
*
* @param name Name of the metadata to be returned.
* @param defaultValue Value to return if the metadata doesn't exist.
* @return The metadata value.
*/
byte[] get(String name, byte[] defaultValue);
/**
* Returns a metadata value.
*
* @param name Name of the metadata to be returned.
* @param defaultValue Value to return if the metadata doesn't exist.
* @return The metadata value.
*/
String get(String name, String defaultValue);
/**
* Returns a metadata value.
*
* @param name Name of the metadata to be returned.
* @param defaultValue Value to return if the metadata doesn't exist.
* @return The metadata value.
*/
long get(String name, long defaultValue);
/** Returns whether the metadata is available. */
boolean contains(String name);
}

View File

@ -0,0 +1,188 @@
/*
* 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 com.google.android.exoplayer2.upstream.cache;
import static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.cache.Cache.CacheException;
import com.google.android.exoplayer2.upstream.cache.ContentMetadata.Editor;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Tests {@link AbstractContentMetadata}. */
@RunWith(RobolectricTestRunner.class)
public class AbstractContentMetadataTest {
private FakeAbstractContentMetadata contentMetadata;
@Before
public void setUp() throws Exception {
contentMetadata = createAbstractContentMetadata();
}
@Test
public void testContainsReturnsFalseWhenEmpty() throws Exception {
assertThat(contentMetadata.contains("test metadata")).isFalse();
}
@Test
public void testContainsReturnsTrueForInitialValue() throws Exception {
contentMetadata = createAbstractContentMetadata("metadata name", "value");
assertThat(contentMetadata.contains("metadata name")).isTrue();
}
@Test
public void testGetReturnsDefaultValueWhenValueIsNotAvailable() throws Exception {
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
}
@Test
public void testGetReturnsInitialValue() throws Exception {
contentMetadata = createAbstractContentMetadata("metadata name", "value");
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
}
@Test
public void testEditReturnsAnEditor() throws Exception {
assertThat(contentMetadata.edit()).isNotNull();
}
@Test
public void testEditReturnsAnotherEditorEveryTime() throws Exception {
assertThat(contentMetadata.edit()).isNotEqualTo(contentMetadata.edit());
}
@Test
public void testCommitWithoutEditDoesNotFail() throws Exception {
Editor editor = contentMetadata.edit();
editor.commit();
}
@Test
public void testAddNewMetadata() throws Exception {
Editor editor = contentMetadata.edit();
editor.set("metadata name", "value");
editor.commit();
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
}
@Test
public void testAddNewIntMetadata() throws Exception {
Editor editor = contentMetadata.edit();
editor.set("metadata name", 5);
editor.commit();
assertThat(contentMetadata.get("metadata name", 0)).isEqualTo(5);
}
@Test
public void testAddNewByteArrayMetadata() throws Exception {
Editor editor = contentMetadata.edit();
byte[] value = {1, 2, 3};
editor.set("metadata name", value);
editor.commit();
assertThat(contentMetadata.get("metadata name", new byte[] {})).isEqualTo(value);
}
@Test
public void testNewMetadataNotWrittenBeforeCommitted() throws Exception {
Editor editor = contentMetadata.edit();
editor.set("metadata name", "value");
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
}
@Test
public void testEditMetadata() throws Exception {
contentMetadata = createAbstractContentMetadata("metadata name", "value");
Editor editor = contentMetadata.edit();
editor.set("metadata name", "edited value");
editor.commit();
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("edited value");
}
@Test
public void testRemoveMetadata() throws Exception {
contentMetadata = createAbstractContentMetadata("metadata name", "value");
Editor editor = contentMetadata.edit();
editor.remove("metadata name");
editor.commit();
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
}
@Test
public void testAddAndRemoveMetadata() throws Exception {
Editor editor = contentMetadata.edit();
editor.set("metadata name", "value");
editor.remove("metadata name");
editor.commit();
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("default value");
}
@Test
public void testRemoveAndAddMetadata() throws Exception {
Editor editor = contentMetadata.edit();
editor.remove("metadata name");
editor.set("metadata name", "value");
editor.commit();
assertThat(contentMetadata.get("metadata name", "default value")).isEqualTo("value");
}
@Test
public void testOnChangeIsCalledWhenMetadataEdited() throws Exception {
contentMetadata =
createAbstractContentMetadata(
"metadata name", "value", "metadata name2", "value2", "metadata name3", "value3");
Editor editor = contentMetadata.edit();
editor.set("metadata name", "edited value");
editor.remove("metadata name2");
editor.commit();
assertThat(contentMetadata.remainingValues).containsExactly("metadata name", "metadata name3");
}
private FakeAbstractContentMetadata createAbstractContentMetadata(String... pairs) {
assertThat(pairs.length % 2).isEqualTo(0);
HashMap<String, byte[]> map = new HashMap<>();
for (int i = 0; i < pairs.length; i += 2) {
map.put(pairs[i], getBytes(pairs[i + 1]));
}
return new FakeAbstractContentMetadata(Collections.unmodifiableMap(map));
}
private static byte[] getBytes(String value) {
return value.getBytes(Charset.forName(C.UTF8_NAME));
}
private static class FakeAbstractContentMetadata extends AbstractContentMetadata {
private ArrayList<String> remainingValues;
private FakeAbstractContentMetadata(Map<String, byte[]> metadataValues) {
super(metadataValues);
}
@Override
protected void onChange(Map<String, byte[]> metadataValues) throws CacheException {
remainingValues = new ArrayList<>(metadataValues.keySet());
}
}
}