Skip to main content
summaryrefslogtreecommitdiffstats
blob: 5f033a4576e8b60484f5a6ce13437376c7b92e90 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/*******************************************************************************
 * Copyright (c) 2004, 2007 Boeing.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Boeing - initial API and implementation
 *******************************************************************************/
package org.eclipse.osee.framework.jdk.core.type;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A Map of keys to multiple values. Collections of values are stored in the Map. The type of Collection can be
 * specified at construction, if desired. All Collections returned by methods are backed by the HashCollection, so
 * changes to the HashCollection are reflected in the Collection, and vice-versa. However, modifications to the
 * Collection outside of this class are generally discouraged because removal of the last item would then not guarantee
 * removal of the key. The mapping also contains a "plus" object. This object can store additional information about the
 * key. At construction a class of type IPlusProvider must be provided. This provider will generate instances of this
 * "plus" object whenever a new key is added to the Map. When all of the items in the Collection are removed, the entire
 * key is removed from the table - therefore, the "plus" object is only available as long as the Collection for a given
 * key is not empty.
 * 
 * @author David Diepenbrock
 */
public class HashCollectionPlus<K, V, O> {

   private boolean isSynchronized;
   private Class<? extends Collection> collectionType;
   private IPlusProvider<O> plusProvider;

   private Map<K, Pair<Collection<V>, O>> map;
   public static final Class<? extends Collection> DEFAULT_COLLECTION_TYPE = ArrayList.class;

   /********************************************************************************************************************
    * Constructors
    *******************************************************************************************************************/

   /**
    * @param isSynchronized - If true, the Map & Collection will both be synchronized using the
    * Collections.synchronizedMap & Collections.synchronizedCollection. otherwise, this class will not be synchronzied
    * and therefore not threadsafe.
    * @param collectionType The type of collection to use to as the values within the HashMap.
    * @see HashMap#HashMap(int, float)
    */
   public HashCollectionPlus(boolean isSynchronized, Class<? extends Collection> collectionType, int initialCapacity, float loadFactor, IPlusProvider<O> plusProvider) {

      if (isSynchronized) {
         map = new ConcurrentHashMap<K, Pair<Collection<V>, O>>(initialCapacity, loadFactor);
      } else {
         map = new HashMap<K, Pair<Collection<V>, O>>(initialCapacity, loadFactor);
      }

      this.isSynchronized = isSynchronized;
      this.collectionType = collectionType;
      this.plusProvider = plusProvider;
   }

   /**
    * @param isSynchronized - If true, the Map & Collection will both be synchronized using the
    * Collections.synchronizedMap & Collections.synchronizedCollection. otherwise, this class will not be synchronzied
    * and therefore not threadsafe.
    * @param collectionType - The type of collection to use to as the values within the HashMap.
    * @see HashMap#HashMap(int)
    */
   public HashCollectionPlus(boolean isSynchronized, Class<? extends Collection> collectionType, int initialCapacity, IPlusProvider<O> plusProvider) {
      if (isSynchronized) {
         map = new ConcurrentHashMap<K, Pair<Collection<V>, O>>(initialCapacity);
      } else {
         map = new HashMap<K, Pair<Collection<V>, O>>(initialCapacity);
      }

      this.isSynchronized = isSynchronized;
      this.collectionType = collectionType;
      this.plusProvider = plusProvider;
   }

   /**
    * @param isSynchronized - If true, the Map & Collection will both be synchronized using the
    * Collections.synchronizedMap & Collections.synchronizedCollection. otherwise, this class will not be synchronzied
    * and therefore not threadsafe.
    * @param collectionType - The type of collection to use to as the values within the HashMap.
    * @see HashMap#HashMap()
    */
   public HashCollectionPlus(boolean isSynchronized, Class<? extends Collection> collectionType, IPlusProvider<O> plusProvider) {
      if (isSynchronized) {
         map = new ConcurrentHashMap<K, Pair<Collection<V>, O>>();
      } else {
         map = new HashMap<K, Pair<Collection<V>, O>>();
      }

      this.isSynchronized = isSynchronized;
      this.collectionType = collectionType;
      this.plusProvider = plusProvider;
   }

   /**
    * Creates an unsynchronized HashCollectionPlus using a default Collection type (ArrayList)
    * 
    * @see HashMap#HashMap(int, float)
    */
   public HashCollectionPlus(int initialCapacity, float loadFactor, IPlusProvider<O> plusProvider) {
      this(false, DEFAULT_COLLECTION_TYPE, initialCapacity, loadFactor, plusProvider);
   }

   /**
    * Creates an unsynchronized HashCollectionPlus using a default Collection type (ArrayList)
    * 
    * @see HashMap#HashMap(int)
    */
   public HashCollectionPlus(int initialCapacity, IPlusProvider<O> plusProvider) {
      this(false, DEFAULT_COLLECTION_TYPE, initialCapacity, plusProvider);
   }

   /**
    * Creates an unsynchronized HashCollectionPlus using a default Collection type (ArrayList)
    * 
    * @see HashMap#HashMap()
    */
   public HashCollectionPlus(IPlusProvider<O> plusProvider) {
      this(false, DEFAULT_COLLECTION_TYPE, plusProvider);
   }

   /********************************************************************************************************************
    * Methods
    *******************************************************************************************************************/

   /**
    * Adds the value to the collection specified by the key. If there is not a collection for the given key, a new
    * collection is created and added to the hash.
    * 
    * @param key The key whose collection we will add value to.
    * @param value The value to be added.
    * @return the collection containing value and all other items associated with the key.
    */
   @SuppressWarnings("unchecked")
   public Collection<V> put(K key, V value) {
      Pair<Collection<V>, O> objectPair = map.get(key);
      if (objectPair == null) {
         try {
            Collection<V> items;
            if (isSynchronized) {
               items = Collections.synchronizedCollection(collectionType.newInstance());
            } else {
               items = collectionType.newInstance();
            }

            objectPair = new Pair(items, plusProvider.newObject());
            map.put(key, objectPair);
         } catch (InstantiationException ex) {
            ex.printStackTrace();
            return null;
         } catch (IllegalAccessException ex) {
            ex.printStackTrace();
            return null;
         }
      }
      objectPair.getFirst().add(value);
      return objectPair.getFirst();
   }

   /**
    * Adds all of the items in the Collection values to the collection for the specified key.
    * 
    * @param key The key to add the values to
    * @param values The values to be added
    * @return The collection for the key, containing all values.
    */
   public Collection<V> put(K key, Collection<V> values) {
      Collection<V> items = null;

      for (V value : values) {
         if (items == null) {
            items = this.put(key, value);
         } else {
            items.add(value);
         }
      }
      return items;
   }

   /**
    * @param key The key whose collection we will remove value from.
    * @param value The value to be removed
    * @return true iff the value was removed from the collection for key.
    */
   public boolean removeValue(K key, V value) {
      Pair<Collection<V>, O> objectPair = map.get(key);

      if (objectPair != null) {
         Collection<V> items = objectPair.getFirst();
         if (items != null) {
            if (items.remove(value)) {
               if (items.isEmpty()) {
                  map.remove(key);
               }
               return true;
            }
         }
      }
      return false;
   }

   public Collection<V> removeValues(K key) {
      Pair<Collection<V>, O> objectPair = map.remove(key);
      Collection<V> toReturn;
      if (objectPair != null) {
         toReturn = objectPair.getFirst();
      } else {
         toReturn = Collections.emptyList();
      }
      return toReturn;
   }

   /**
    * Returns the Collection of items for this key, or null if the key does not exist.
    * 
    * @return Return value collection reference
    */
   public Collection<V> getValues(K key) {
      Pair<Collection<V>, O> objectPair = map.get(key);
      if (objectPair != null) {
         return objectPair.getFirst();
      }
      return null;
   }

   /**
    * Returns the Collection all items
    * 
    * @return Return value collection reference
    */
   public List<V> getValues() {
      List<V> values = new ArrayList<V>();
      for (Pair<Collection<V>, O> objectPair : map.values()) {
         if (objectPair != null) {
            values.addAll(objectPair.getFirst());
         }
      }
      return values;
   }

   /**
    * Returns the "plus" object associated with the key, or null if the key does not exist.
    * 
    * @return Return object reference
    */
   public O getPlusObject(K key) {
      Pair<Collection<V>, O> objectPair = map.get(key);
      if (objectPair != null) {
         return map.get(key).getSecond();
      }
      return null;
   }

   public Set<K> keySet() {
      return map.keySet();
   }

   public void clear() {
      map.clear();
   }

   public boolean containsKey(K key) {
      return map.containsKey(key);
   }

   public boolean isEmpty() {
      return map.isEmpty();
   }

   /**
    * The total number of key-value combinations
    */
   public int size() {
      int size = 0;
      Set<K> keySet = keySet();

      synchronized (map) {
         for (K key : keySet) {
            size += getValues(key).size();
         }
      }
      return size;
   }

   /**
    * @return whether the map contains this value
    */
   public boolean containsValue(Object value) {
      for (Pair<Collection<V>, O> objectPair : map.values()) {
         if (objectPair != null) {
            for (V tempValue : objectPair.getFirst()) {
               if (value.equals(tempValue)) {
                  return true;
               }
            }
         }
      }
      return false;
   }

   @Override
   public String toString() {
      return map.toString();
   }
}

Back to the top