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}