diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java index 9791fdb46f..f14d6782ae 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndexTest.java @@ -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()); } } - } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java index da7b4bfd60..35c9b24c8e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContent.java @@ -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 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)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java index bd97ea8880..d16a9cbe91 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/CachedContentIndex.java @@ -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; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java index 7bff31853a..ac3864e366 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadata.java @@ -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. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataMutations.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataMutations.java new file mode 100644 index 0000000000..0f58aa46b6 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/ContentMetadataMutations.java @@ -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 editedValues; + private final List 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 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 getEditedValues() { + return editedValues; + } + + private ContentMetadataMutations checkAndSet(String name, Object value) { + editedValues.put(name, Assertions.checkNotNull(value)); + removedValues.remove(name); + return this; + } +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java index f35b29db14..7aa420d68e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadata.java @@ -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 metadataValues) - throws CacheException; - } - private final Map 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 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 removedValues, Map editedValues) - throws CacheException { - synchronized (metadata) { - 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)); - } + private void applyMutations(ContentMetadataMutations mutations) { + List removedValues = mutations.getRemovedValues(); + for (int i = 0; i < removedValues.size(); i++) { + metadata.remove(removedValues.get(i)); + } + Map 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 editedValues; - private final ArrayList 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(); + 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(); } } + } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java index 780cc8182d..5554329e0e 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/cache/DefaultContentMetadataTest.java @@ -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 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 remainingValues; - - @Override - public void onChange(DefaultContentMetadata contentMetadata, Map metadataValues) - throws CacheException { - remainingValues = new ArrayList<>(metadataValues.keySet()); + mutations.set(pairs[i], pairs[i + 1]); } + return new DefaultContentMetadata(new DefaultContentMetadata(), mutations); } }