зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1553354 - Add org.mozilla.gecko.MultiMap. r=snorp
Differential Revision: https://phabricator.services.mozilla.com/D57510 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
dafbe96c98
Коммит
f70dd054b3
|
@ -0,0 +1,212 @@
|
|||
package org.mozilla.geckoview.test;
|
||||
|
||||
import android.support.test.filters.MediumTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.MultiMap;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@MediumTest
|
||||
public class MultiMapTest {
|
||||
@Test
|
||||
public void emptyMap() {
|
||||
final MultiMap<String, String> map = new MultiMap<>();
|
||||
|
||||
assertThat(map.get("not-present").isEmpty(), is(true));
|
||||
assertThat(map.containsKey("not-present"), is(false));
|
||||
assertThat(map.containsEntry("not-present", "nope"), is(false));
|
||||
assertThat(map.size(), is(0));
|
||||
assertThat(map.asMap().size(), is(0));
|
||||
assertThat(map.remove("not-present"), nullValue());
|
||||
assertThat(map.remove("not-present", "nope"), is(false));
|
||||
assertThat(map.keySet().size(), is(0));
|
||||
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyMapWithCapacity() {
|
||||
final MultiMap<String, String> map = new MultiMap<>(10);
|
||||
|
||||
assertThat(map.get("not-present").isEmpty(), is(true));
|
||||
assertThat(map.containsKey("not-present"), is(false));
|
||||
assertThat(map.containsEntry("not-present", "nope"), is(false));
|
||||
assertThat(map.size(), is(0));
|
||||
assertThat(map.asMap().size(), is(0));
|
||||
assertThat(map.remove("not-present"), nullValue());
|
||||
assertThat(map.remove("not-present", "nope"), is(false));
|
||||
assertThat(map.keySet().size(), is(0));
|
||||
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addMultipleValues() {
|
||||
final MultiMap<String, String> map = new MultiMap<>();
|
||||
map.add("test", "value1");
|
||||
map.add("test", "value2");
|
||||
map.add("test2", "value3");
|
||||
|
||||
assertThat(map.containsEntry("test", "value1"), is(true));
|
||||
assertThat(map.containsEntry("test", "value2"), is(true));
|
||||
assertThat(map.containsEntry("test2", "value3"), is(true));
|
||||
|
||||
assertThat(map.containsEntry("test3", "value1"), is(false));
|
||||
assertThat(map.containsEntry("test", "value3"), is(false));
|
||||
|
||||
List<String> values = map.get("test");
|
||||
assertThat(values.contains("value1"), is(true));
|
||||
assertThat(values.contains("value2"), is(true));
|
||||
assertThat(values.contains("value3"), is(false));
|
||||
assertThat(values.size(), is(2));
|
||||
|
||||
List<String> values2 = map.get("test2");
|
||||
assertThat(values2.contains("value1"), is(false));
|
||||
assertThat(values2.contains("value2"), is(false));
|
||||
assertThat(values2.contains("value3"), is(true));
|
||||
assertThat(values2.size(), is(1));
|
||||
|
||||
assertThat(map.size(), is(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remove() {
|
||||
final MultiMap<String, String> map = new MultiMap<>();
|
||||
map.add("test", "value1");
|
||||
map.add("test", "value2");
|
||||
map.add("test2", "value3");
|
||||
|
||||
assertThat(map.size(), is(2));
|
||||
|
||||
List<String> values = map.remove("test");
|
||||
|
||||
assertThat(values.size(), is(2));
|
||||
assertThat(values.contains("value1"), is(true));
|
||||
assertThat(values.contains("value2"), is(true));
|
||||
|
||||
assertThat(map.size(), is(1));
|
||||
|
||||
assertThat(map.containsKey("test"), is(false));
|
||||
assertThat(map.containsEntry("test", "value1"), is(false));
|
||||
assertThat(map.containsEntry("test", "value2"), is(false));
|
||||
assertThat(map.get("test").size(), is(0));
|
||||
|
||||
assertThat(map.get("test2").size(), is(1));
|
||||
assertThat(map.get("test2").contains("value3"), is(true));
|
||||
assertThat(map.containsEntry("test2", "value3"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAllValuesRemovesKey() {
|
||||
final MultiMap<String, String> map = new MultiMap<>();
|
||||
map.add("test", "value1");
|
||||
map.add("test", "value2");
|
||||
map.add("test2", "value3");
|
||||
|
||||
assertThat(map.remove("test", "value1"), is(true));
|
||||
assertThat(map.containsEntry("test", "value1"), is(false));
|
||||
assertThat(map.containsEntry("test", "value2"), is(true));
|
||||
assertThat(map.get("test").size(), is(1));
|
||||
assertThat(map.get("test").contains("value2"), is(true));
|
||||
|
||||
assertThat(map.remove("test", "value2"), is(true));
|
||||
|
||||
assertThat(map.remove("test", "value3"), is(false));
|
||||
assertThat(map.remove("test2", "value4"), is(false));
|
||||
|
||||
assertThat(map.containsKey("test"), is(false));
|
||||
assertThat(map.containsKey("test2"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keySet() {
|
||||
final MultiMap<String, String> map = new MultiMap<>();
|
||||
map.add("test", "value1");
|
||||
map.add("test", "value2");
|
||||
map.add("test2", "value3");
|
||||
|
||||
Set<String> keys = map.keySet();
|
||||
|
||||
assertThat(keys.size(), is(2));
|
||||
assertThat(keys.contains("test"), is(true));
|
||||
assertThat(keys.contains("test2"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clear() {
|
||||
final MultiMap<String, String> map = new MultiMap<>();
|
||||
map.add("test", "value1");
|
||||
map.add("test", "value2");
|
||||
map.add("test2", "value3");
|
||||
|
||||
assertThat(map.size(), is(2));
|
||||
|
||||
map.clear();
|
||||
|
||||
assertThat(map.size(), is(0));
|
||||
assertThat(map.containsKey("test"), is(false));
|
||||
assertThat(map.containsKey("test2"), is(false));
|
||||
assertThat(map.containsEntry("test", "value1"), is(false));
|
||||
assertThat(map.containsEntry("test", "value2"), is(false));
|
||||
assertThat(map.containsEntry("test2", "value3"), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asMap() {
|
||||
final MultiMap<String, String> map = new MultiMap<>();
|
||||
map.add("test", "value1");
|
||||
map.add("test", "value2");
|
||||
map.add("test2", "value3");
|
||||
|
||||
final Map<String, List<String>> asMap = map.asMap();
|
||||
|
||||
assertThat(asMap.size(), is(2));
|
||||
|
||||
assertThat(asMap.get("test").size(), is(2));
|
||||
assertThat(asMap.get("test").contains("value1"), is(true));
|
||||
assertThat(asMap.get("test").contains("value2"), is(true));
|
||||
|
||||
assertThat(asMap.get("test2").size(), is(1));
|
||||
assertThat(asMap.get("test2").contains("value3"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addAll() {
|
||||
final MultiMap<String, String> map = new MultiMap<>();
|
||||
map.add("test", "value1");
|
||||
|
||||
assertThat(map.get("test").size(), is(1));
|
||||
|
||||
// Existing key test
|
||||
List<String> values = map.addAll("test", Arrays.asList("value2", "value3"));
|
||||
|
||||
assertThat(values.size(), is(3));
|
||||
assertThat(values.contains("value1"), is(true));
|
||||
assertThat(values.contains("value2"), is(true));
|
||||
assertThat(values.contains("value3"), is(true));
|
||||
|
||||
assertThat(map.containsEntry("test", "value1"), is(true));
|
||||
assertThat(map.containsEntry("test", "value2"), is(true));
|
||||
assertThat(map.containsEntry("test", "value3"), is(true));
|
||||
|
||||
// New key test
|
||||
List<String> values2 = map.addAll("test2", Arrays.asList("value4", "value5"));
|
||||
assertThat(values2.size(), is(2));
|
||||
assertThat(values2.contains("value4"), is(true));
|
||||
assertThat(values2.contains("value5"), is(true));
|
||||
|
||||
assertThat(map.containsEntry("test2", "value4"), is(true));
|
||||
assertThat(map.containsEntry("test2", "value5"), is(true));
|
||||
}
|
||||
}
|
|
@ -22,9 +22,7 @@ import android.util.Log;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
@RobocopTarget
|
||||
|
@ -44,12 +42,12 @@ public final class EventDispatcher extends JNIObject {
|
|||
private static final int DEFAULT_BACKGROUND_EVENTS_COUNT = 64; // Empirically measured
|
||||
|
||||
// GeckoBundle-based events.
|
||||
private final Map<String, List<BundleEventListener>> mGeckoThreadListeners =
|
||||
new HashMap<String, List<BundleEventListener>>(DEFAULT_GECKO_EVENTS_COUNT);
|
||||
private final Map<String, List<BundleEventListener>> mUiThreadListeners =
|
||||
new HashMap<String, List<BundleEventListener>>(DEFAULT_UI_EVENTS_COUNT);
|
||||
private final Map<String, List<BundleEventListener>> mBackgroundThreadListeners =
|
||||
new HashMap<String, List<BundleEventListener>>(DEFAULT_BACKGROUND_EVENTS_COUNT);
|
||||
private final MultiMap<String, BundleEventListener> mGeckoThreadListeners =
|
||||
new MultiMap<>(DEFAULT_GECKO_EVENTS_COUNT);
|
||||
private final MultiMap<String, BundleEventListener> mUiThreadListeners =
|
||||
new MultiMap<>(DEFAULT_UI_EVENTS_COUNT);
|
||||
private final MultiMap<String, BundleEventListener> mBackgroundThreadListeners =
|
||||
new MultiMap<>(DEFAULT_BACKGROUND_EVENTS_COUNT);
|
||||
|
||||
private boolean mAttachedToGecko;
|
||||
private final NativeQueue mNativeQueue;
|
||||
|
@ -104,7 +102,7 @@ public final class EventDispatcher extends JNIObject {
|
|||
}
|
||||
|
||||
private <T> void registerListener(final Class<?> listType,
|
||||
final Map<String, List<T>> listenersMap,
|
||||
final MultiMap<String, T> listenersMap,
|
||||
final T listener,
|
||||
final String[] events) {
|
||||
try {
|
||||
|
@ -113,18 +111,10 @@ public final class EventDispatcher extends JNIObject {
|
|||
if (event == null) {
|
||||
continue;
|
||||
}
|
||||
List<T> listeners = listenersMap.get(event);
|
||||
if (listeners == null) {
|
||||
// Java doesn't let us put Class<? extends List<T>> as the type for listType.
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<? extends List<T>> type = (Class) listType;
|
||||
listeners = type.newInstance();
|
||||
listenersMap.put(event, listeners);
|
||||
}
|
||||
if (!BuildConfig.RELEASE_OR_BETA && listeners.contains(listener)) {
|
||||
if (!BuildConfig.RELEASE_OR_BETA && listenersMap.containsEntry(event, listener)) {
|
||||
throw new IllegalStateException("Already registered " + event);
|
||||
}
|
||||
listeners.add(listener);
|
||||
listenersMap.add(event, listener);
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
|
@ -132,14 +122,14 @@ public final class EventDispatcher extends JNIObject {
|
|||
}
|
||||
}
|
||||
|
||||
private void checkNotRegisteredElsewhere(final Map<String, ?> allowedMap,
|
||||
private void checkNotRegisteredElsewhere(final MultiMap<String, ?> allowedMap,
|
||||
final String[] events) {
|
||||
if (BuildConfig.RELEASE_OR_BETA) {
|
||||
// for performance reasons, we only check for
|
||||
// already-registered listeners in non-release builds.
|
||||
return;
|
||||
}
|
||||
for (final Map<String, ?> listenersMap : Arrays.asList(mGeckoThreadListeners,
|
||||
for (final MultiMap<String, ?> listenersMap : Arrays.asList(mGeckoThreadListeners,
|
||||
mUiThreadListeners,
|
||||
mBackgroundThreadListeners)) {
|
||||
if (listenersMap == allowedMap) {
|
||||
|
@ -147,7 +137,7 @@ public final class EventDispatcher extends JNIObject {
|
|||
}
|
||||
synchronized (listenersMap) {
|
||||
for (final String event : events) {
|
||||
if (listenersMap.get(event) != null) {
|
||||
if (listenersMap.containsKey(event)) {
|
||||
throw new IllegalStateException(
|
||||
"Already registered " + event + " under a different type");
|
||||
}
|
||||
|
@ -156,7 +146,7 @@ public final class EventDispatcher extends JNIObject {
|
|||
}
|
||||
}
|
||||
|
||||
private <T> void unregisterListener(final Map<String, List<T>> listenersMap,
|
||||
private <T> void unregisterListener(final MultiMap<String, T> listenersMap,
|
||||
final T listener,
|
||||
final String[] events) {
|
||||
synchronized (listenersMap) {
|
||||
|
@ -164,9 +154,8 @@ public final class EventDispatcher extends JNIObject {
|
|||
if (event == null) {
|
||||
continue;
|
||||
}
|
||||
List<T> listeners = listenersMap.get(event);
|
||||
if ((listeners == null ||
|
||||
!listeners.remove(listener)) && !BuildConfig.RELEASE_OR_BETA) {
|
||||
|
||||
if (!listenersMap.remove(event, listener) && !BuildConfig.RELEASE_OR_BETA) {
|
||||
throw new IllegalArgumentException(event + " was not registered");
|
||||
}
|
||||
}
|
||||
|
@ -272,7 +261,7 @@ public final class EventDispatcher extends JNIObject {
|
|||
synchronized (mGeckoThreadListeners) {
|
||||
geckoListeners = mGeckoThreadListeners.get(type);
|
||||
}
|
||||
if (geckoListeners != null && !geckoListeners.isEmpty()) {
|
||||
if (!geckoListeners.isEmpty()) {
|
||||
final boolean onGeckoThread = ThreadUtils.isOnGeckoThread();
|
||||
final EventCallback wrappedCallback = JavaCallbackDelegate.wrap(callback);
|
||||
|
||||
|
@ -329,11 +318,11 @@ public final class EventDispatcher extends JNIObject {
|
|||
|
||||
@WrapForJNI
|
||||
public boolean hasListener(final String event) {
|
||||
for (final Map<String, ?> listenersMap : Arrays.asList(mGeckoThreadListeners,
|
||||
for (final MultiMap<String, ?> listenersMap : Arrays.asList(mGeckoThreadListeners,
|
||||
mUiThreadListeners,
|
||||
mBackgroundThreadListeners)) {
|
||||
synchronized (listenersMap) {
|
||||
if (listenersMap.get(event) != null) {
|
||||
if (listenersMap.containsKey(event)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -345,20 +334,14 @@ public final class EventDispatcher extends JNIObject {
|
|||
private boolean dispatchToThread(final String type,
|
||||
final GeckoBundle message,
|
||||
final EventCallback callback,
|
||||
final Map<String, List<BundleEventListener>> listenersMap,
|
||||
final MultiMap<String, BundleEventListener> listenersMap,
|
||||
final Handler thread) {
|
||||
// We need to hold the lock throughout dispatching, to ensure the listeners list
|
||||
// is consistent, while we iterate over it. We don't have to worry about listeners
|
||||
// running for a long time while we have the lock, because the listeners will run
|
||||
// on a separate thread.
|
||||
synchronized (listenersMap) {
|
||||
final List<BundleEventListener> listeners = listenersMap.get(type);
|
||||
if (listeners == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (listeners.isEmpty()) {
|
||||
// There were native listeners, and they're gone.
|
||||
if (!listenersMap.containsKey(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -366,7 +349,7 @@ public final class EventDispatcher extends JNIObject {
|
|||
final EventCallback wrappedCallback = JavaCallbackDelegate.wrap(callback);
|
||||
|
||||
// Event listeners will call | callback.sendError | if applicable.
|
||||
for (final BundleEventListener listener : listeners) {
|
||||
for (final BundleEventListener listener : listenersMap.get(type)) {
|
||||
thread.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
package org.mozilla.gecko;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Defines a map that holds a collection of values against each key.
|
||||
*
|
||||
* @param <K> Key type
|
||||
* @param <T> Value type
|
||||
*/
|
||||
public class MultiMap<K, T> {
|
||||
private HashMap<K, List<T>> mMap;
|
||||
private final List<T> mEmptyList = Collections.unmodifiableList(new ArrayList<>());
|
||||
|
||||
/**
|
||||
* Creates a MultiMap with specified initial capacity.
|
||||
*
|
||||
* @param count Initial capacity
|
||||
*/
|
||||
public MultiMap(final int count) {
|
||||
mMap = new HashMap<>(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MultiMap with default initial capacity.
|
||||
*/
|
||||
public MultiMap() {
|
||||
mMap = new HashMap<>();
|
||||
}
|
||||
|
||||
private void ensure(final K key) {
|
||||
if (!mMap.containsKey(key)) {
|
||||
mMap.put(key, new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A map of key to the list of values associated to it
|
||||
*/
|
||||
public Map<K, List<T>> asMap() {
|
||||
return mMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of keys present in this map
|
||||
*/
|
||||
public int size() {
|
||||
return mMap.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether this map is empty or not
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return mMap.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a key is present in this map.
|
||||
*
|
||||
* @param key the key to check
|
||||
* @return True if the map contains this key, false otherwise.
|
||||
*/
|
||||
public boolean containsKey(final @Nullable K key) {
|
||||
return mMap.containsKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a (key, value) pair is present in this map.
|
||||
*
|
||||
* @param key the key to check
|
||||
* @param value the value to check
|
||||
* @return true if there is a value associated to the given key, false otherwise
|
||||
*/
|
||||
public boolean containsEntry(final @Nullable K key, final @Nullable T value) {
|
||||
if (!mMap.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mMap.get(key).contains(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the values associated with the given key.
|
||||
*
|
||||
* @param key the key to check
|
||||
* @return the list of values associated with keys, an empty list if no values are associated
|
||||
* with key.
|
||||
*/
|
||||
@NonNull
|
||||
public List<T> get(final @Nullable K key) {
|
||||
if (!mMap.containsKey(key)) {
|
||||
return mEmptyList;
|
||||
}
|
||||
|
||||
return mMap.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a (key, value) mapping to this map.
|
||||
*
|
||||
* @param key the key to add
|
||||
* @param value the value to add
|
||||
*/
|
||||
@Nullable
|
||||
public void add(final @NonNull K key, final @NonNull T value) {
|
||||
ensure(key);
|
||||
mMap.get(key).add(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a list of values to the given key.
|
||||
*
|
||||
* @param key the key to add
|
||||
* @param values the list of values to add
|
||||
* @return the final list of values or null if no value was added
|
||||
*/
|
||||
@Nullable
|
||||
public List<T> addAll(final @NonNull K key, final @NonNull List<T> values) {
|
||||
if (values == null || values.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ensure(key);
|
||||
|
||||
final List<T> result = mMap.get(key);
|
||||
result.addAll(values);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all mappings for the given key.
|
||||
*
|
||||
* @param key the key
|
||||
*
|
||||
* @return values associated with the key or null if no values was present.
|
||||
*/
|
||||
@Nullable
|
||||
public List<T> remove(final @Nullable K key) {
|
||||
return mMap.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a (key, value) mapping from this map
|
||||
*
|
||||
* @param key the key to remove
|
||||
* @param value the value to remove
|
||||
*
|
||||
* @return true if the (key, value) mapping was present, false otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public boolean remove(final @Nullable K key, final @Nullable T value) {
|
||||
if (!mMap.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<T> values = mMap.get(key);
|
||||
boolean wasPresent = values.remove(value);
|
||||
|
||||
if (values.isEmpty()) {
|
||||
mMap.remove(key);
|
||||
}
|
||||
|
||||
return wasPresent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all mappings from this map.
|
||||
*/
|
||||
public void clear() {
|
||||
mMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a set with all the keys for this map.
|
||||
*/
|
||||
@NonNull
|
||||
public Set<K> keySet() {
|
||||
return mMap.keySet();
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче