2015-02-05 11:04:38 +01:00
|
|
|
/**
|
|
|
|
*
|
2019-02-04 08:59:39 +01:00
|
|
|
* Copyright © 2015-2019 Florian Schmaus
|
2015-02-05 11:04:38 +01:00
|
|
|
*
|
|
|
|
* 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 org.jivesoftware.smack.util;
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.LinkedHashSet;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2019-06-10 16:58:38 +02:00
|
|
|
import java.util.Map.Entry;
|
2015-02-05 11:04:38 +01:00
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A lightweight implementation of a MultiMap, that is a Map that is able to hold multiple values for every key.
|
|
|
|
* <p>
|
2015-10-13 22:31:11 +02:00
|
|
|
* This MultiMap uses a {@link LinkedHashMap} together with a {@link ArrayList} in order to keep the order of its entries.
|
2015-02-05 11:04:38 +01:00
|
|
|
* </p>
|
|
|
|
*
|
|
|
|
* @param <K> the type of the keys the map uses.
|
|
|
|
* @param <V> the type of the values the map uses.
|
|
|
|
*/
|
2019-10-24 15:45:08 +02:00
|
|
|
public class MultiMap<K, V> implements TypedCloneable<MultiMap<K, V>> {
|
2015-02-05 11:04:38 +01:00
|
|
|
|
|
|
|
/**
|
2015-10-13 22:31:11 +02:00
|
|
|
* The constant value {@value}.
|
2015-02-05 11:04:38 +01:00
|
|
|
*/
|
|
|
|
public static final int DEFAULT_MAP_SIZE = 6;
|
|
|
|
|
2015-10-13 22:31:11 +02:00
|
|
|
private static final int ENTRY_LIST_SIZE = 3;
|
2015-02-05 11:04:38 +01:00
|
|
|
|
2015-10-13 22:31:11 +02:00
|
|
|
private final Map<K, List<V>> map;
|
2015-02-05 11:04:38 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a new MultiMap with a initial capacity of {@link #DEFAULT_MAP_SIZE}.
|
|
|
|
*/
|
|
|
|
public MultiMap() {
|
|
|
|
this(DEFAULT_MAP_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a new MultiMap.
|
|
|
|
*
|
|
|
|
* @param size the initial capacity.
|
|
|
|
*/
|
|
|
|
public MultiMap(int size) {
|
2019-06-10 16:58:38 +02:00
|
|
|
this(new LinkedHashMap<>(size));
|
|
|
|
}
|
|
|
|
|
|
|
|
private MultiMap(Map<K, List<V>> map) {
|
|
|
|
this.map = map;
|
2015-02-05 11:04:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public int size() {
|
|
|
|
int size = 0;
|
2015-10-13 22:31:11 +02:00
|
|
|
for (List<V> list : map.values()) {
|
|
|
|
size += list.size();
|
2015-02-05 11:04:38 +01:00
|
|
|
}
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isEmpty() {
|
|
|
|
return map.isEmpty();
|
|
|
|
}
|
|
|
|
|
2019-06-10 21:43:38 +02:00
|
|
|
public boolean containsKey(K key) {
|
2015-02-05 11:04:38 +01:00
|
|
|
return map.containsKey(key);
|
|
|
|
}
|
|
|
|
|
2019-06-10 21:43:38 +02:00
|
|
|
public boolean containsValue(V value) {
|
2015-10-13 22:31:11 +02:00
|
|
|
for (List<V> list : map.values()) {
|
|
|
|
if (list.contains(value)) {
|
2015-02-05 11:04:38 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the first value for the given key, or <code>null</code> if there are no entries.
|
|
|
|
*
|
2019-08-30 12:08:30 +02:00
|
|
|
* @param key TODO javadoc me please
|
2015-02-05 11:04:38 +01:00
|
|
|
* @return the first value or null.
|
|
|
|
*/
|
2019-06-10 21:43:38 +02:00
|
|
|
public V getFirst(K key) {
|
2015-10-13 22:31:11 +02:00
|
|
|
List<V> res = getAll(key);
|
2015-02-05 11:04:38 +01:00
|
|
|
if (res.isEmpty()) {
|
|
|
|
return null;
|
|
|
|
} else {
|
|
|
|
return res.iterator().next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all values for the given key. Returns the empty set if there are none.
|
|
|
|
* <p>
|
|
|
|
* Changes to the returned set will update the underlying MultiMap if the return set is not empty.
|
|
|
|
* </p>
|
|
|
|
*
|
2019-08-30 12:08:30 +02:00
|
|
|
* @param key TODO javadoc me please
|
2015-02-05 11:04:38 +01:00
|
|
|
* @return all values for the given key.
|
|
|
|
*/
|
2019-06-10 21:43:38 +02:00
|
|
|
public List<V> getAll(K key) {
|
2015-10-13 22:31:11 +02:00
|
|
|
List<V> res = map.get(key);
|
2015-02-05 11:04:38 +01:00
|
|
|
if (res == null) {
|
2015-10-13 22:31:11 +02:00
|
|
|
res = Collections.emptyList();
|
2015-02-05 11:04:38 +01:00
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean put(K key, V value) {
|
|
|
|
boolean keyExisted;
|
2015-10-13 22:31:11 +02:00
|
|
|
List<V> list = map.get(key);
|
|
|
|
if (list == null) {
|
|
|
|
list = new ArrayList<>(ENTRY_LIST_SIZE);
|
|
|
|
list.add(value);
|
|
|
|
map.put(key, list);
|
2015-02-05 11:04:38 +01:00
|
|
|
keyExisted = false;
|
|
|
|
} else {
|
2015-10-13 22:31:11 +02:00
|
|
|
list.add(value);
|
2015-02-05 11:04:38 +01:00
|
|
|
keyExisted = true;
|
|
|
|
}
|
|
|
|
return keyExisted;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all mappings for the given key and returns the first value if there where mappings or <code>null</code> if not.
|
|
|
|
*
|
2019-08-30 12:08:30 +02:00
|
|
|
* @param key TODO javadoc me please
|
2015-02-05 11:04:38 +01:00
|
|
|
* @return the first value of the given key or null.
|
|
|
|
*/
|
2019-06-10 21:43:38 +02:00
|
|
|
public V remove(K key) {
|
2015-10-13 22:31:11 +02:00
|
|
|
List<V> res = map.remove(key);
|
2015-02-05 11:04:38 +01:00
|
|
|
if (res == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2019-07-24 09:18:39 +02:00
|
|
|
assert !res.isEmpty();
|
2015-02-05 11:04:38 +01:00
|
|
|
return res.iterator().next();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove the mapping of the given key to the value.
|
|
|
|
* <p>
|
|
|
|
* Returns <code>true</code> if the mapping was removed and <code>false</code> if the mapping did not exist.
|
|
|
|
* </p>
|
|
|
|
*
|
2019-08-30 12:08:30 +02:00
|
|
|
* @param key TODO javadoc me please
|
|
|
|
* @param value TODO javadoc me please
|
2015-02-05 11:04:38 +01:00
|
|
|
* @return true if the mapping was removed, false otherwise.
|
|
|
|
*/
|
2019-06-10 21:43:38 +02:00
|
|
|
public boolean removeOne(K key, V value) {
|
2015-10-13 22:31:11 +02:00
|
|
|
List<V> list = map.get(key);
|
|
|
|
if (list == null) {
|
2015-02-05 11:04:38 +01:00
|
|
|
return false;
|
|
|
|
}
|
2015-10-13 22:31:11 +02:00
|
|
|
boolean res = list.remove(value);
|
|
|
|
if (list.isEmpty()) {
|
2015-02-05 11:04:38 +01:00
|
|
|
// Remove the key also if the value set is now empty
|
|
|
|
map.remove(key);
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2019-02-04 08:59:39 +01:00
|
|
|
/**
|
|
|
|
* Remove the given number of values for a given key. May return less values then requested.
|
|
|
|
*
|
|
|
|
* @param key the key to remove from.
|
|
|
|
* @param num the number of values to remove.
|
|
|
|
* @return a list of the removed values.
|
|
|
|
* @since 4.4.0
|
|
|
|
*/
|
|
|
|
public List<V> remove(K key, int num) {
|
|
|
|
List<V> values = map.get(key);
|
|
|
|
if (values == null) {
|
|
|
|
return Collections.emptyList();
|
|
|
|
}
|
|
|
|
|
|
|
|
final int resultSize = values.size() > num ? num : values.size();
|
|
|
|
final List<V> result = new ArrayList<>(resultSize);
|
|
|
|
for (int i = 0; i < resultSize; i++) {
|
|
|
|
result.add(values.get(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (values.isEmpty()) {
|
|
|
|
map.remove(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-02-05 11:04:38 +01:00
|
|
|
public void putAll(Map<? extends K, ? extends V> map) {
|
|
|
|
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
|
|
|
|
put(entry.getKey(), entry.getValue());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void clear() {
|
|
|
|
map.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
public Set<K> keySet() {
|
|
|
|
return map.keySet();
|
|
|
|
}
|
|
|
|
|
2015-02-23 10:49:33 +01:00
|
|
|
/**
|
|
|
|
* Returns a new list containing all values of this multi map.
|
|
|
|
*
|
|
|
|
* @return a new list with all values.
|
|
|
|
*/
|
2015-02-05 11:04:38 +01:00
|
|
|
public List<V> values() {
|
|
|
|
List<V> values = new ArrayList<>(size());
|
2015-10-13 22:31:11 +02:00
|
|
|
for (List<V> list : map.values()) {
|
|
|
|
values.addAll(list);
|
2015-02-05 11:04:38 +01:00
|
|
|
}
|
|
|
|
return values;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Set<Map.Entry<K, V>> entrySet() {
|
|
|
|
Set<Map.Entry<K, V>> entrySet = new LinkedHashSet<>(size());
|
2015-10-13 22:31:11 +02:00
|
|
|
for (Map.Entry<K, List<V>> entries : map.entrySet()) {
|
2015-02-05 11:04:38 +01:00
|
|
|
K key = entries.getKey();
|
|
|
|
for (V value : entries.getValue()) {
|
|
|
|
entrySet.add(new SimpleMapEntry<>(key, value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return entrySet;
|
|
|
|
}
|
|
|
|
|
2019-06-10 16:58:38 +02:00
|
|
|
public MultiMap<K, V> asUnmodifiableMultiMap() {
|
|
|
|
LinkedHashMap<K, List<V>> mapCopy = new LinkedHashMap<>(map.size());
|
|
|
|
for (Entry<K, List<V>> entry : map.entrySet()) {
|
|
|
|
K key = entry.getKey();
|
|
|
|
List<V> values = entry.getValue();
|
|
|
|
|
|
|
|
mapCopy.put(key, Collections.unmodifiableList(values));
|
|
|
|
}
|
|
|
|
|
|
|
|
return new MultiMap<K, V>(Collections.unmodifiableMap(mapCopy));
|
|
|
|
}
|
|
|
|
|
2019-10-24 15:45:08 +02:00
|
|
|
@Override
|
|
|
|
public MultiMap<K, V> clone() {
|
|
|
|
Map<K, List<V>> clonedMap = new LinkedHashMap<>(map.size());
|
|
|
|
|
|
|
|
// TODO: Use Map.forEach() once Smack's minimum Android API is 24 or higher.
|
|
|
|
for (Entry<K, List<V>> entry : map.entrySet()) {
|
|
|
|
List<V> clonedList = CollectionUtil.newListWith(entry.getValue());
|
|
|
|
clonedMap.put(entry.getKey(), clonedList);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new MultiMap<>(clonedMap);
|
|
|
|
}
|
|
|
|
|
2015-04-06 10:45:12 +02:00
|
|
|
private static final class SimpleMapEntry<K, V> implements Map.Entry<K, V> {
|
2015-02-05 11:04:38 +01:00
|
|
|
|
|
|
|
private final K key;
|
|
|
|
private V value;
|
|
|
|
|
|
|
|
private SimpleMapEntry(K key, V value) {
|
|
|
|
this.key = key;
|
|
|
|
this.value = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public K getKey() {
|
|
|
|
return key;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public V getValue() {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public V setValue(V value) {
|
|
|
|
V tmp = this.value;
|
|
|
|
this.value = value;
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|