Make CachedContent store content length in ContentMetadata

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=188742379
This commit is contained in:
eguven 2018-03-12 10:36:23 -07:00 committed by Oliver Woodman
parent c38e7b1aeb
commit 00a7306fd8
7 changed files with 211 additions and 262 deletions

View File

@ -14,9 +14,7 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Set;
/**
* Tests {@link CachedContentIndex}.
*/
/** Tests {@link CachedContentIndex}. */
public class CachedContentIndexTest extends InstrumentationTestCase {
private final byte[] testIndexV1File = {
@ -53,14 +51,14 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
final String key3 = "key3";
// Add two CachedContents with add methods
CachedContent cachedContent1 = new CachedContent(5, key1, 10);
CachedContent cachedContent1 = new CachedContent(5, key1);
index.addNew(cachedContent1);
CachedContent cachedContent2 = index.getOrAdd(key2);
assertThat(cachedContent1.id != cachedContent2.id).isTrue();
// add a span
File cacheSpanFile = SimpleCacheSpanTest
.createCacheSpanFile(cacheDir, cachedContent1.id, 10, 20, 30);
File cacheSpanFile =
SimpleCacheSpanTest.createCacheSpanFile(cacheDir, cachedContent1.id, 10, 20, 30);
SimpleCacheSpan span = SimpleCacheSpan.createCacheEntry(cacheSpanFile, index);
assertThat(span).isNotNull();
cachedContent1.addSpan(span);
@ -115,8 +113,12 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
}
public void testStoreV1() throws Exception {
index.addNew(new CachedContent(2, "KLMNO", 2560));
index.addNew(new CachedContent(5, "ABCDE", 10));
CachedContent cachedContent1 = new CachedContent(2, "KLMNO");
cachedContent1.setLength(2560);
index.addNew(cachedContent1);
CachedContent cachedContent2 = new CachedContent(5, "ABCDE");
cachedContent2.setLength(10);
index.addNew(cachedContent2);
index.store();
@ -165,8 +167,8 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
byte[] key = "Bar12345Bar12345".getBytes(C.UTF8_NAME); // 128 bit key
byte[] key2 = "Foo12345Foo12345".getBytes(C.UTF8_NAME); // 128 bit key
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir, key),
new CachedContentIndex(cacheDir, key));
assertStoredAndLoadedEqual(
new CachedContentIndex(cacheDir, key), new CachedContentIndex(cacheDir, key));
// Rename the index file from the test above
File file1 = new File(cacheDir, CachedContentIndex.FILE_NAME);
@ -174,8 +176,8 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
assertThat(file1.renameTo(file2)).isTrue();
// Write a new index file
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir, key),
new CachedContentIndex(cacheDir, key));
assertStoredAndLoadedEqual(
new CachedContentIndex(cacheDir, key), new CachedContentIndex(cacheDir, key));
assertThat(file1.length()).isEqualTo(file2.length());
// Assert file content is different
@ -187,8 +189,8 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
boolean threw = false;
try {
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir, key),
new CachedContentIndex(cacheDir, key2));
assertStoredAndLoadedEqual(
new CachedContentIndex(cacheDir, key), new CachedContentIndex(cacheDir, key2));
} catch (AssertionError e) {
threw = true;
}
@ -197,8 +199,8 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
.isTrue();
try {
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir, key),
new CachedContentIndex(cacheDir));
assertStoredAndLoadedEqual(
new CachedContentIndex(cacheDir, key), new CachedContentIndex(cacheDir));
} catch (AssertionError e) {
threw = true;
}
@ -207,18 +209,18 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
.isTrue();
// Non encrypted index file can be read even when encryption key provided.
assertStoredAndLoadedEqual(new CachedContentIndex(cacheDir),
new CachedContentIndex(cacheDir, key));
assertStoredAndLoadedEqual(
new CachedContentIndex(cacheDir), new CachedContentIndex(cacheDir, key));
// Test multiple store() calls
CachedContentIndex index = new CachedContentIndex(cacheDir, key);
index.addNew(new CachedContent(15, "key3", 110));
index.addNew(new CachedContent(15, "key3"));
index.store();
assertStoredAndLoadedEqual(index, new CachedContentIndex(cacheDir, key));
}
public void testRemoveEmptyNotLockedCachedContent() throws Exception {
CachedContent cachedContent = new CachedContent(5, "key1", 10);
CachedContent cachedContent = new CachedContent(5, "key1");
index.addNew(cachedContent);
index.maybeRemove(cachedContent.key);
@ -227,7 +229,7 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
}
public void testCantRemoveNotEmptyCachedContent() throws Exception {
CachedContent cachedContent = new CachedContent(5, "key1", 10);
CachedContent cachedContent = new CachedContent(5, "key1");
index.addNew(cachedContent);
File cacheSpanFile =
SimpleCacheSpanTest.createCacheSpanFile(cacheDir, cachedContent.id, 10, 20, 30);
@ -240,7 +242,7 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
}
public void testCantRemoveLockedCachedContent() throws Exception {
CachedContent cachedContent = new CachedContent(5, "key1", 10);
CachedContent cachedContent = new CachedContent(5, "key1");
cachedContent.setLocked(true);
index.addNew(cachedContent);
@ -251,7 +253,7 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
private void assertStoredAndLoadedEqual(CachedContentIndex index, CachedContentIndex index2)
throws IOException {
index.addNew(new CachedContent(5, "key1", 10));
index.addNew(new CachedContent(5, "key1"));
index.getOrAdd("key2");
index.store();
@ -264,5 +266,4 @@ public class CachedContentIndexTest extends InstrumentationTestCase {
assertThat(index2.get(key).getSpans()).isEqualTo(index.get(key).getSpans());
}
}
}

View File

@ -28,14 +28,16 @@ import java.util.TreeSet;
*/
/*package*/ final class CachedContent {
private static final String EXOPLAYER_METADATA_NAME_PREFIX = "exo_";
private static final String METADATA_NAME_LENGTH = EXOPLAYER_METADATA_NAME_PREFIX + "len";
/** The cache file id that uniquely identifies the original stream. */
public final int id;
/** The cache key that uniquely identifies the original stream. */
public final String key;
/** The cached spans of this content. */
private final TreeSet<SimpleCacheSpan> cachedSpans;
/** The length of the original stream, or {@link C#LENGTH_UNSET} if the length is unknown. */
private long length;
/** Metadata values. */
private DefaultContentMetadata metadata;
/** Whether the content is locked. */
private boolean locked;
@ -45,8 +47,13 @@ import java.util.TreeSet;
* @param input Input stream containing values needed to initialize CachedContent instance.
* @throws IOException If an error occurs during reading values.
*/
public CachedContent(DataInputStream input) throws IOException {
this(input.readInt(), input.readUTF(), input.readLong());
public static CachedContent readFromStream(DataInputStream input) throws IOException {
int id = input.readInt();
String key = input.readUTF();
long length = input.readLong();
CachedContent cachedContent = new CachedContent(id, key);
cachedContent.setLength(length);
return cachedContent;
}
/**
@ -54,12 +61,11 @@ import java.util.TreeSet;
*
* @param id The cache file id.
* @param key The cache stream key.
* @param length The length of the original stream.
*/
public CachedContent(int id, String key, long length) {
public CachedContent(int id, String key) {
this.id = id;
this.key = key;
this.length = length;
this.metadata = new DefaultContentMetadata();
this.cachedSpans = new TreeSet<>();
}
@ -72,17 +78,21 @@ import java.util.TreeSet;
public void writeToStream(DataOutputStream output) throws IOException {
output.writeInt(id);
output.writeUTF(key);
output.writeLong(length);
output.writeLong(getLength());
}
/** Returns the length of the content. */
/**
* Returns the length of the original stream, or {@link C#LENGTH_UNSET} if the length is unknown.
*/
public long getLength() {
return length;
return metadata.get(METADATA_NAME_LENGTH, C.LENGTH_UNSET);
}
/** Sets the length of the content. */
public void setLength(long length) {
this.length = length;
ContentMetadataMutations mutations =
new ContentMetadataMutations().set(METADATA_NAME_LENGTH, length);
metadata = new DefaultContentMetadata(metadata, mutations);
}
/** Returns whether the content is locked. */
@ -194,6 +204,7 @@ import java.util.TreeSet;
/** Calculates a hash code for the header of this {@code CachedContent}. */
public int headerHashCode() {
long length = getLength();
int result = id;
result = 31 * result + key.hashCode();
result = 31 * result + (int) (length ^ (length >>> 32));

View File

@ -45,9 +45,7 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* This class maintains the index of cached content.
*/
/** Maintains the index of cached content. */
/*package*/ class CachedContentIndex {
public static final String FILE_NAME = "cached_content_index.exi";
@ -259,7 +257,7 @@ import javax.crypto.spec.SecretKeySpec;
int count = input.readInt();
int hashCode = 0;
for (int i = 0; i < count; i++) {
CachedContent cachedContent = new CachedContent(input);
CachedContent cachedContent = CachedContent.readFromStream(input);
add(cachedContent);
hashCode += cachedContent.headerHashCode();
}
@ -339,7 +337,8 @@ import javax.crypto.spec.SecretKeySpec;
private CachedContent addNew(String key, long length) {
int id = getNewId(idToKey);
CachedContent cachedContent = new CachedContent(id, key, length);
CachedContent cachedContent = new CachedContent(id, key);
cachedContent.setLength(length);
addNew(cachedContent);
return cachedContent;
}

View File

@ -15,63 +15,9 @@
*/
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.
*

View File

@ -0,0 +1,103 @@
/*
* 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.util.Assertions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Defines multiple mutations on metadata value which are applied atomically. */
public class ContentMetadataMutations {
private final Map<String, Object> editedValues;
private final List<String> removedValues;
/** Constructs a DefaultMetadataMutations. */
public ContentMetadataMutations() {
editedValues = new HashMap<>();
removedValues = new ArrayList<>();
}
/**
* Adds a mutation to set a metadata value. Passing {@code null} as {@code 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.
*/
public ContentMetadataMutations set(String name, String value) {
return checkAndSet(name, value);
}
/**
* Adds a mutation to set a metadata value.
*
* @param name The name of the metadata value.
* @param value The value to be set.
* @return This Editor instance, for convenience.
*/
public ContentMetadataMutations set(String name, long value) {
return checkAndSet(name, value);
}
/**
* Adds a mutation to set a metadata value. 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.
*/
public ContentMetadataMutations set(String name, byte[] value) {
return checkAndSet(name, value);
}
/**
* Adds a mutation to remove a metadata value.
*
* @param name The name of the metadata value.
* @return This Editor instance, for convenience.
*/
public ContentMetadataMutations remove(String name) {
removedValues.add(name);
editedValues.remove(name);
return this;
}
/**
* Returns a list of names of metadata values to be removed. The returned array shouldn't be
* modified.
*/
public List<String> getRemovedValues() {
return removedValues;
}
/**
* Returns a map of metadata name, value pairs to be set. The returned map and the values in it
* shouldn't be modified.
*/
public Map<String, Object> getEditedValues() {
return editedValues;
}
private ContentMetadataMutations checkAndSet(String name, Object value) {
editedValues.put(name, Assertions.checkNotNull(value));
removedValues.remove(name);
return this;
}
}

View File

@ -15,59 +15,30 @@
*/
package com.google.android.exoplayer2.upstream.cache;
import android.support.annotation.Nullable;
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.List;
import java.util.Map;
/** Default implementation of {@link ContentMetadata}. Values are stored as byte arrays. */
public final class DefaultContentMetadata implements ContentMetadata {
/** Listener for metadata change event. */
public interface ChangeListener {
/**
* Called when any metadata value is changed or removed.
*
* @param contentMetadata The reporting instance.
* @param metadataValues All metadata name, value pairs. It shouldn't be accessed out of this
* method.
*/
void onChange(DefaultContentMetadata contentMetadata, Map<String, byte[]> metadataValues)
throws CacheException;
}
private final Map<String, byte[]> metadata;
@Nullable private ChangeListener changeListener;
/** Constructs an empty {@link DefaultContentMetadata}. */
public DefaultContentMetadata() {
this.metadata = new HashMap<>();
}
/**
* Constructs a {@link DefaultContentMetadata} using name, value pairs data which was passed to
* {@link ChangeListener#onChange(DefaultContentMetadata, Map)}.
*
* @param metadata Initial name, value pairs.
* Constructs a {@link DefaultContentMetadata} by copying metadata values from {@code other} and
* applying {@code mutations}.
*/
public DefaultContentMetadata(Map<String, byte[]> metadata) {
this.metadata = new HashMap<>(metadata);
}
/** Sets a {@link ChangeListener}. This method can be called once. */
public void setChangeListener(ChangeListener changeListener) {
Assertions.checkState(this.changeListener == null);
this.changeListener = Assertions.checkNotNull(changeListener);
}
@Override
public final Editor edit() {
return new EditorImpl();
public DefaultContentMetadata(DefaultContentMetadata other, ContentMetadataMutations mutations) {
this.metadata = new HashMap<>(other.metadata);
applyMutations(mutations);
}
@Override
@ -112,60 +83,28 @@ public final class DefaultContentMetadata implements ContentMetadata {
}
}
private void apply(ArrayList<String> removedValues, Map<String, byte[]> editedValues)
throws CacheException {
synchronized (metadata) {
private void applyMutations(ContentMetadataMutations mutations) {
List<String> removedValues = mutations.getRemovedValues();
for (int i = 0; i < removedValues.size(); i++) {
metadata.remove(removedValues.get(i));
}
metadata.putAll(editedValues);
if (changeListener != null) {
changeListener.onChange(this, Collections.unmodifiableMap(metadata));
}
Map<String, Object> editedValues = mutations.getEditedValues();
for (String name : editedValues.keySet()) {
Object value = editedValues.get(name);
metadata.put(name, getBytes(value));
}
}
private class EditorImpl implements Editor {
private final Map<String, byte[]> editedValues;
private final ArrayList<String> removedValues;
private EditorImpl() {
editedValues = new HashMap<>();
removedValues = new ArrayList<>();
private static byte[] getBytes(Object value) {
if (value instanceof Long) {
return ByteBuffer.allocate(8).putLong((Long) value).array();
} else if (value instanceof String) {
return ((String) value).getBytes(Charset.forName(C.UTF8_NAME));
} else if (value instanceof byte[]) {
return (byte[]) value;
} else {
throw new IllegalStateException();
}
}
@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

@ -17,15 +17,6 @@ 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 com.google.android.exoplayer2.upstream.cache.DefaultContentMetadata.ChangeListener;
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;
@ -36,11 +27,9 @@ import org.robolectric.RobolectricTestRunner;
public class DefaultContentMetadataTest {
private DefaultContentMetadata contentMetadata;
private FakeChangeListener changeListener;
@Before
public void setUp() throws Exception {
changeListener = new FakeChangeListener();
contentMetadata = createAbstractContentMetadata();
}
@ -67,124 +56,85 @@ public class DefaultContentMetadataTest {
}
@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();
public void testEmptyMutationDoesNotFail() throws Exception {
ContentMetadataMutations mutations = new ContentMetadataMutations();
new DefaultContentMetadata(new DefaultContentMetadata(), mutations);
}
@Test
public void testAddNewMetadata() throws Exception {
Editor editor = contentMetadata.edit();
editor.set("metadata name", "value");
editor.commit();
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", "value");
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
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();
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", 5);
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
assertThat(contentMetadata.get("metadata name", 0)).isEqualTo(5);
}
@Test
public void testAddNewByteArrayMetadata() throws Exception {
Editor editor = contentMetadata.edit();
ContentMetadataMutations mutations = new ContentMetadataMutations();
byte[] value = {1, 2, 3};
editor.set("metadata name", value);
editor.commit();
mutations.set("metadata name", value);
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
assertThat(contentMetadata.get("metadata name", new byte[] {})).isEqualTo(value);
}
@Test
public void testNewMetadataNotWrittenBeforeCommitted() throws Exception {
Editor editor = contentMetadata.edit();
editor.set("metadata name", "value");
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.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();
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", "edited value");
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
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();
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.remove("metadata name");
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
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();
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.set("metadata name", "value");
mutations.remove("metadata name");
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
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();
ContentMetadataMutations mutations = new ContentMetadataMutations();
mutations.remove("metadata name");
mutations.set("metadata name", "value");
contentMetadata = new DefaultContentMetadata(contentMetadata, mutations);
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(changeListener.remainingValues).containsExactly("metadata name", "metadata name3");
}
private DefaultContentMetadata createAbstractContentMetadata(String... pairs) {
assertThat(pairs.length % 2).isEqualTo(0);
HashMap<String, byte[]> map = new HashMap<>();
ContentMetadataMutations mutations = new ContentMetadataMutations();
for (int i = 0; i < pairs.length; i += 2) {
map.put(pairs[i], getBytes(pairs[i + 1]));
}
DefaultContentMetadata metadata = new DefaultContentMetadata(Collections.unmodifiableMap(map));
metadata.setChangeListener(changeListener);
return metadata;
}
private static byte[] getBytes(String value) {
return value.getBytes(Charset.forName(C.UTF8_NAME));
}
private static class FakeChangeListener implements ChangeListener {
private ArrayList<String> remainingValues;
@Override
public void onChange(DefaultContentMetadata contentMetadata, Map<String, byte[]> metadataValues)
throws CacheException {
remainingValues = new ArrayList<>(metadataValues.keySet());
mutations.set(pairs[i], pairs[i + 1]);
}
return new DefaultContentMetadata(new DefaultContentMetadata(), mutations);
}
}