001/** 002 * 003 * Copyright © 2015-2024 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.util; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.LinkedHashMap; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027/** 028 * A lightweight implementation of a MultiMap, that is a Map that is able to hold multiple values for every key. 029 * <p> 030 * This MultiMap uses a {@link LinkedHashMap} together with a {@link ArrayList} in order to keep the order of its entries. 031 * </p> 032 * 033 * @param <K> the type of the keys the map uses. 034 * @param <V> the type of the values the map uses. 035 */ 036public class MultiMap<K, V> { 037 038 /** 039 * The constant value {@value}. 040 */ 041 public static final int DEFAULT_MAP_SIZE = 6; 042 043 private static final int ENTRY_LIST_SIZE = 3; 044 045 private final Map<K, List<V>> map; 046 047 /** 048 * Constructs a new MultiMap with a initial capacity of {@link #DEFAULT_MAP_SIZE}. 049 */ 050 public MultiMap() { 051 this(DEFAULT_MAP_SIZE); 052 } 053 054 /** 055 * Constructs a new MultiMap. 056 * 057 * @param size the initial capacity. 058 */ 059 public MultiMap(int size) { 060 this(new LinkedHashMap<>(size)); 061 } 062 063 private MultiMap(Map<K, List<V>> map) { 064 this.map = map; 065 } 066 067 public int size() { 068 int size = 0; 069 for (List<V> list : map.values()) { 070 size += list.size(); 071 } 072 return size; 073 } 074 075 public boolean isEmpty() { 076 return map.isEmpty(); 077 } 078 079 public boolean containsKey(K key) { 080 return map.containsKey(key); 081 } 082 083 public boolean containsValue(V value) { 084 for (List<V> list : map.values()) { 085 if (list.contains(value)) { 086 return true; 087 } 088 } 089 return false; 090 } 091 092 /** 093 * Get the first value for the given key, or <code>null</code> if there are no entries. 094 * 095 * @param key TODO javadoc me please 096 * @return the first value or null. 097 */ 098 public V getFirst(K key) { 099 List<V> res = getAll(key); 100 if (res.isEmpty()) { 101 return null; 102 } else { 103 return res.iterator().next(); 104 } 105 } 106 107 /** 108 * Get all values for the given key. Returns the empty set if there are none. 109 * <p> 110 * Changes to the returned set will update the underlying MultiMap if the return set is not empty. 111 * </p> 112 * 113 * @param key TODO javadoc me please 114 * @return all values for the given key. 115 */ 116 public List<V> getAll(K key) { 117 List<V> res = map.get(key); 118 if (res == null) { 119 res = Collections.emptyList(); 120 } 121 return res; 122 } 123 124 public boolean put(K key, V value) { 125 return putInternal(key, list -> list.add(value)); 126 } 127 128 public boolean putFirst(K key, V value) { 129 return putInternal(key, list -> list.add(0, value)); 130 } 131 132 private boolean putInternal(K key, Consumer<List<V>> valueListConsumer) { 133 boolean keyExisted; 134 List<V> list = map.get(key); 135 if (list == null) { 136 list = new ArrayList<>(ENTRY_LIST_SIZE); 137 map.put(key, list); 138 keyExisted = false; 139 } else { 140 keyExisted = true; 141 } 142 143 valueListConsumer.accept(list); 144 145 return keyExisted; 146 } 147 148 /** 149 * Removes all mappings for the given key and returns the first value if there where mappings or <code>null</code> if not. 150 * 151 * @param key TODO javadoc me please 152 * @return the first value of the given key or null. 153 */ 154 public V remove(K key) { 155 List<V> res = map.remove(key); 156 if (res == null) { 157 return null; 158 } 159 assert !res.isEmpty(); 160 return res.iterator().next(); 161 } 162 163 /** 164 * Remove the mapping of the given key to the value. 165 * <p> 166 * Returns <code>true</code> if the mapping was removed and <code>false</code> if the mapping did not exist. 167 * </p> 168 * 169 * @param key TODO javadoc me please 170 * @param value TODO javadoc me please 171 * @return true if the mapping was removed, false otherwise. 172 */ 173 public boolean removeOne(K key, V value) { 174 List<V> list = map.get(key); 175 if (list == null) { 176 return false; 177 } 178 boolean res = list.remove(value); 179 if (list.isEmpty()) { 180 // Remove the key also if the value set is now empty 181 map.remove(key); 182 } 183 return res; 184 } 185 186 /** 187 * Remove the given number of values for a given key. May return less values than requested. 188 * 189 * @param key the key to remove from. 190 * @param num the number of values to remove. 191 * @return a list of the removed values. 192 * @since 4.4.0 193 */ 194 @SuppressWarnings("MixedMutabilityReturnType") 195 public List<V> remove(K key, int num) { 196 List<V> values = map.get(key); 197 if (values == null) { 198 return Collections.emptyList(); 199 } 200 201 final int resultSize = values.size() > num ? num : values.size(); 202 final List<V> result = new ArrayList<>(resultSize); 203 for (int i = 0; i < resultSize; i++) { 204 result.add(values.get(0)); 205 } 206 207 if (values.isEmpty()) { 208 map.remove(key); 209 } 210 211 return result; 212 } 213 214 public void putAll(Map<? extends K, ? extends V> map) { 215 for (Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 216 put(entry.getKey(), entry.getValue()); 217 } 218 } 219 220 public void clear() { 221 map.clear(); 222 } 223 224 public Set<K> keySet() { 225 return map.keySet(); 226 } 227 228 /** 229 * Returns a new list containing all values of this multi map. 230 * 231 * @return a new list with all values. 232 */ 233 public List<V> values() { 234 List<V> values = new ArrayList<>(size()); 235 for (List<V> list : map.values()) { 236 values.addAll(list); 237 } 238 return values; 239 } 240 241 public Set<Map.Entry<K, V>> entrySet() { 242 Set<Map.Entry<K, V>> entrySet = new LinkedHashSet<>(size()); 243 for (Map.Entry<K, List<V>> entries : map.entrySet()) { 244 K key = entries.getKey(); 245 for (V value : entries.getValue()) { 246 entrySet.add(new SimpleMapEntry<>(key, value)); 247 } 248 } 249 return entrySet; 250 } 251 252 public MultiMap<K, V> asUnmodifiableMultiMap() { 253 LinkedHashMap<K, List<V>> mapCopy = new LinkedHashMap<>(map.size()); 254 for (Map.Entry<K, List<V>> entry : map.entrySet()) { 255 K key = entry.getKey(); 256 List<V> values = entry.getValue(); 257 258 mapCopy.put(key, Collections.unmodifiableList(values)); 259 } 260 261 return new MultiMap<K, V>(Collections.unmodifiableMap(mapCopy)); 262 } 263 264 @Override 265 public MultiMap<K, V> clone() { 266 Map<K, List<V>> clonedMap = new LinkedHashMap<>(map.size()); 267 268 map.forEach((k, v) -> { 269 List<V> clonedList = CollectionUtil.newListWith(v); 270 clonedMap.put(k, clonedList); 271 }); 272 273 return new MultiMap<>(clonedMap); 274 } 275 276 private static final class SimpleMapEntry<K, V> implements Map.Entry<K, V> { 277 278 private final K key; 279 private V value; 280 281 private SimpleMapEntry(K key, V value) { 282 this.key = key; 283 this.value = value; 284 } 285 286 @Override 287 public K getKey() { 288 return key; 289 } 290 291 @Override 292 public V getValue() { 293 return value; 294 } 295 296 @Override 297 public V setValue(V value) { 298 V tmp = this.value; 299 this.value = value; 300 return tmp; 301 } 302 } 303 304}