001/*-------------------------------------------------------------------------+ 002| | 003| Copyright 2005-2011 The ConQAT Project | 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.conqat.lib.commons.collections; 018 019import java.io.Serializable; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.function.Supplier; 027 028/** 029 * A collection map deals with a map of collections, i.e. each key can store 030 * multiple elements. Depending on the collection implementation chosen, this 031 * can also be interpreted as a multi map. 032 * <p> 033 * If you deal with the basic case of {@link Map}s and {@link List}s, use the 034 * {@link ListMap}, which is much easier to apply. 035 * 036 * @param <K> 037 * the key type. 038 * @param <V> 039 * the value type (i.e. the values stored in the collections). 040 * @param <C> 041 * the collection type, which is made explicit at the interface. 042 */ 043public abstract class CollectionMap<K, V, C extends Collection<V>> implements Serializable, Iterable<Entry<K, C>> { 044 045 private static final long serialVersionUID = 1L; 046 047 /** The underlying map. */ 048 private final Map<K, C> map; 049 050 /** Constructor. */ 051 public CollectionMap() { 052 map = createUnderlyingMap(); 053 } 054 055 /** 056 * Create a new instance of the underlying map, called exactly once during the 057 * constructor. Can be used to prepopulate the list with values if necessary. 058 */ 059 protected Map<K, C> createUnderlyingMap() { 060 return new HashMap<>(); 061 } 062 063 /** 064 * Returns a new instance of the underlying collection to be used as value for 065 * the underlaying map. 066 */ 067 protected abstract C createNewCollection(); 068 069 /** 070 * Returns the collection stored under the given key (or null). Modifying the 071 * collection will directly affect this object. 072 */ 073 public C getCollection(K key) { 074 return map.get(key); 075 } 076 077 /** 078 * Returns the collection stored under the given key (or the given default if no 079 * collection is stored). Modifying the collection will directly affect this 080 * object. 081 */ 082 public C getCollectionOrElse(K key, C defaultCollection) { 083 if (map.containsKey(key)) { 084 return map.get(key); 085 } 086 return defaultCollection; 087 } 088 089 /** 090 * Returns the collection stored under the given key (or an empty collection). 091 * If there was a collection stored under the key, modifying the collection will 092 * directly affect this object. 093 */ 094 public C getCollectionOrEmpty(K key) { 095 C result = getCollection(key); 096 if (result == null) { 097 return createNewCollection(); 098 } 099 return result; 100 } 101 102 /** 103 * Adds a value to the collection associated with the given key. 104 * 105 * @return <code>true</code> if the collection associated with the given key 106 * changed as a result of the call. 107 */ 108 public boolean add(K key, V value) { 109 return getOrCreateCollection(key).add(value); 110 } 111 112 /** 113 * Adds all values to the collection associated with the given key. 114 * 115 * @return <code>true</code> if the collection associated with the given key 116 * changed as a result of the call. 117 */ 118 public boolean addAll(K key, Collection<? extends V> values) { 119 return getOrCreateCollection(key).addAll(values); 120 } 121 122 /** 123 * Returns the collection stored under the given key (or creates a new one). 124 */ 125 /* package */C getOrCreateCollection(K key) { 126 C collection = map.get(key); 127 if (collection == null) { 128 collection = createNewCollection(); 129 map.put(key, collection); 130 } 131 return collection; 132 } 133 134 /** Adds all elements from another collection map. */ 135 public void addAll(CollectionMap<K, V, C> other) { 136 for (K key : other.getKeys()) { 137 C collection = other.getCollection(key); 138 if (collection != null) { 139 addAll(key, collection); 140 } 141 } 142 } 143 144 /** Adds all elements from a Java map. */ 145 public void addAll(Map<K, C> other) { 146 for (Entry<K, C> entry : other.entrySet()) { 147 C collection = entry.getValue(); 148 if (collection != null) { 149 addAll(entry.getKey(), collection); 150 } 151 } 152 } 153 154 /** Returns whether an element is contained. */ 155 public boolean contains(K key, V value) { 156 C collection = map.get(key); 157 if (collection == null) { 158 return false; 159 } 160 return collection.contains(value); 161 } 162 163 /** 164 * Removes an element. 165 * 166 * @return true if an element was removed as a result of this call. 167 */ 168 public boolean remove(K key, V value) { 169 C collection = map.get(key); 170 if (collection == null) { 171 return false; 172 } 173 return collection.remove(value); 174 } 175 176 /** 177 * Check if a (possibly empty) collection is present for a given key. 178 */ 179 public boolean containsCollection(K key) { 180 return map.containsKey(key); 181 } 182 183 /** 184 * Removes the collection stored for a key. 185 * 186 * @return true if a collection was removed as a result of this call. 187 */ 188 public boolean removeCollection(K key) { 189 return map.remove(key) != null; 190 } 191 192 /** Get the keys. */ 193 public UnmodifiableSet<K> getKeys() { 194 return CollectionUtils.asUnmodifiable(map.keySet()); 195 } 196 197 /** Return all values from all collections. */ 198 public C getValues() { 199 C result = createNewCollection(); 200 for (C values : map.values()) { 201 result.addAll(values); 202 } 203 return result; 204 } 205 206 /** 207 * Returns the size of this map, i.e. number of key-value mappings in this map. 208 */ 209 public int size() { 210 return map.size(); 211 } 212 213 /** Return the total count of values over all collections. */ 214 public int getValueCount() { 215 int result = 0; 216 for (C values : map.values()) { 217 result += values.size(); 218 } 219 return result; 220 } 221 222 /** Clears the underlying map and thus all contents. */ 223 public void clear() { 224 map.clear(); 225 } 226 227 /** 228 * Converts the {@link CollectionMap} to a map with arrays 229 * 230 * @param type 231 * Type of the target array 232 */ 233 public Map<K, V[]> collectionsToArrays(Class<V> type) { 234 Map<K, V[]> map = new HashMap<>(); 235 for (K key : getKeys()) { 236 map.put(key, CollectionUtils.toArray(getCollection(key), type)); 237 } 238 return map; 239 } 240 241 @Override 242 public String toString() { 243 return map.toString(); 244 } 245 246 /** {@inheritDoc} */ 247 @Override 248 public Iterator<Entry<K, C>> iterator() { 249 return map.entrySet().iterator(); 250 } 251 252 /** 253 * Helper method to create a new CollectionMap with a given type of Collection 254 * initializer. Usage e.g.: 255 * <code>CollectionMap.createWithCollectionInitializer(() -> new LinkedList<RatingPartition>());</code> 256 */ 257 public static <K, V, C extends Collection<V>> CollectionMap<K, V, C> createWithCollectionSupplier( 258 Supplier<C> collectionInitializer) { 259 return new CollectionMap<K, V, C>() { 260 261 private static final long serialVersionUID = 1L; 262 263 @Override 264 protected C createNewCollection() { 265 return collectionInitializer.get(); 266 } 267 }; 268 } 269 270}