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.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026/**
027 * A 2-dimensional hash map. Allows storage of items identified by two different
028 * keys. This can be used to store the following data structure:
029 * 
030 * <ul>
031 * <li>Project A
032 * <ul>
033 * <li>Dan &mdash; <b>Testing </b></li>
034 * <li>Flo &mdash; <b>Documentation </b></li>
035 * </ul>
036 * </li>
037 * <li>Project B
038 * <ul>
039 * <li>Flo &mdash; <b>Design </b></li>
040 * <li>Dan &mdash; <b>QA </b></li>
041 * <li>Markus &mdash; <b>CM </b></li>
042 * <li>Jorge &mdash; <b>Testing </b></li>
043 * </ul>
044 * </li>
045 * </ul>
046 */
047public class TwoDimHashMap<K1, K2, V> {
048
049        /** The first level map. */
050        private final Map<K1, Map<K2, V>> data;
051
052        /** Create a new doubly hashed map. */
053        public TwoDimHashMap() {
054                data = new HashMap<K1, Map<K2, V>>();
055        }
056
057        /** Create a new two dimensional map using the data in the given map. */
058        public TwoDimHashMap(Map<K1, Map<K2, V>> map) {
059                data = map;
060        }
061
062        /** Put all values of another {@link TwoDimHashMap} into this map. */
063        public void putAll(TwoDimHashMap<K1, K2, V> otherMap) {
064                for (K1 key1 : otherMap.getFirstKeys()) {
065                        for (K2 key2 : otherMap.getSecondKeys(key1)) {
066                                V value = otherMap.getValue(key1, key2);
067                                putValue(key1, key2, value);
068                        }
069                }
070        }
071
072        /**
073         * Puts the given value into this map under the given keys. Any potentially
074         * existing value will be overwritten.
075         * 
076         * @param key1
077         *            first level key
078         * @param key2
079         *            second level key
080         * @param value
081         *            the value
082         */
083        public void putValue(K1 key1, K2 key2, V value) {
084                Map<K2, V> map = data.get(key1);
085                if (map == null) {
086                        map = new HashMap<K2, V>();
087                        data.put(key1, map);
088                }
089                map.put(key2, value);
090        }
091
092        /**
093         * Get a value by specifying first and second level key.
094         * 
095         * @param firstKey
096         *            first level key
097         * @param secondKey
098         *            second level key
099         * @return the value. Is <code>null</code> if first or second level key does
100         *         not exist or if <code>null</code> was explicitly stored.
101         */
102        public V getValue(K1 firstKey, K2 secondKey) {
103                Map<K2, V> map = data.get(firstKey);
104                if (map == null) {
105                        return null;
106                }
107                return map.get(secondKey);
108        }
109
110        /**
111         * Returns whether the given key combination is available in the map.
112         * 
113         * @param firstKey
114         *            first level key
115         * @param secondKey
116         *            second level key
117         */
118        public boolean containsKey(K1 firstKey, K2 secondKey) {
119                Map<K2, V> map = data.get(firstKey);
120                if (map == null) {
121                        return false;
122                }
123                return map.containsKey(secondKey);
124        }
125
126        /**
127         * Get all values referenced by a first level key.
128         * 
129         * @param firstKey
130         *            the first level key
131         * @return a list of values referenced by the specified first level key
132         */
133        public Collection<V> getValuesByFirstKey(K1 firstKey) {
134                Map<K2, V> map = data.get(firstKey);
135                if (map == null) {
136                        return null;
137                }
138                return map.values();
139
140        }
141
142        /**
143         * Get all first level keys.
144         */
145        public Set<K1> getFirstKeys() {
146                return data.keySet();
147        }
148
149        /**
150         * Get all the second level keys stored under the given first level key.
151         * 
152         * @param firstKey
153         *            the first level key.
154         * @return all second level keys for a first level key.
155         */
156        public Set<K2> getSecondKeys(K1 firstKey) {
157                Map<K2, V> map = data.get(firstKey);
158                if (map == null) {
159                        return CollectionUtils.emptySet();
160                }
161                return map.keySet();
162        }
163
164        /**
165         * Get all values referenced by a second level key.
166         * 
167         * <b>Note: </b> This method's complexity is linear in the number of first
168         * level keys.
169         * 
170         * @param secondKey
171         *            the second level key
172         * @return a new list of values referenced by the specified second level key
173         */
174        public List<V> getValuesBySecondKey(K2 secondKey) {
175                ArrayList<V> result = new ArrayList<V>();
176
177                for (Map<K2, V> map : data.values()) {
178                        if (map.containsKey(secondKey)) {
179                                result.add(map.get(secondKey));
180                        }
181                }
182
183                return result;
184        }
185
186        /**
187         * Get all values stored in the map.
188         * 
189         * @return a new list of all values.
190         */
191        public List<V> getValues() {
192                ArrayList<V> result = new ArrayList<V>();
193
194                for (Map<K2, V> map : data.values()) {
195                        result.addAll(map.values());
196                }
197
198                return result;
199        }
200
201        /**
202         * Get size of the map.
203         * 
204         * @return the number of values stored in this map.
205         */
206        public int getSize() {
207                int size = 0;
208                for (Map<K2, V> map : data.values()) {
209                        size += map.size();
210                }
211                return size;
212        }
213
214        /**
215         * Check if the map is empty, i.e. no values are stored in it.
216         */
217        public boolean isEmpty() {
218                return getSize() == 0;
219        }
220
221        /**
222         * Get the size of the (second) map stored for a first key.
223         * 
224         * @return the size or 0 if the first level key wasn't found.
225         */
226        public int getSecondSize(K1 key1) {
227                Map<K2, V> map = data.get(key1);
228                if (map == null) {
229                        return 0;
230                }
231                return map.size();
232        }
233
234        /**
235         * Clear the whole map.
236         */
237        public void clear() {
238                data.clear();
239        }
240
241        /**
242         * Removes the value associated with the given key combination.
243         * 
244         * @return previous value associated with specified keys, or
245         *         <code>null</code> if there was no mapping for those keys. A
246         *         <code>null</code> return can also indicate that the map
247         *         previously associated <code>null</code> with the specified keys.
248         */
249        public V remove(K1 key1, K2 key2) {
250                Map<K2, V> map = data.get(key1);
251                if (map == null) {
252                        return null;
253                }
254
255                if (!map.containsKey(key2)) {
256                        return null;
257                }
258
259                V result = map.remove(key2);
260
261                if (map.isEmpty()) {
262                        data.remove(key1);
263                }
264
265                return result;
266        }
267
268        /**
269         * Remove all values stored under the given first level key.
270         * 
271         * @param key
272         *            first level key
273         * @return <code>true</code> if the given key was present in the map,
274         *         <code>false</code> otherwise.
275         */
276        public boolean remove(K1 key) {
277                Map<K2, V> result = data.remove(key);
278                return result != null;
279        }
280
281        /**
282         * Returns the data stored under the given first-level key as an
283         * unmodifiable map or <code>null</code> if nothing is stored under that
284         * key.
285         */
286        public UnmodifiableMap<K2, V> getSecondMap(K1 key) {
287                Map<K2, V> secondMap = data.get(key);
288                if (secondMap == null) {
289                        return null;
290                }
291                return CollectionUtils.asUnmodifiable(secondMap);
292        }
293}