File: CompositeMap.java

package info (click to toggle)
libcommons-collections4-java 4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 5,568 kB
  • ctags: 8,096
  • sloc: java: 51,622; xml: 2,964; makefile: 6
file content (548 lines) | stat: -rw-r--r-- 23,510 bytes parent folder | download
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
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.collections4.map;

import java.io.Serializable;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections4.set.CompositeSet;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.collection.CompositeCollection;

/**
 * Decorates a map of other maps to provide a single unified view.
 * <p>
 * Changes made to this map will actually be made on the decorated map.
 * Add and remove operations require the use of a pluggable strategy. If no
 * strategy is provided then add and remove are unsupported.
 * <p>
 * <strong>Note that CompositeMap is not synchronized and is not thread-safe.</strong>
 * If you wish to use this map from multiple threads concurrently, you must use
 * appropriate synchronization. The simplest approach is to wrap this map
 * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw
 * exceptions when accessed by concurrent threads without synchronization.
 *
 * @since 3.0
 * @version $Id: CompositeMap.java 1543276 2013-11-19 00:53:14Z ggregory $
 */
public class CompositeMap<K, V> extends AbstractIterableMap<K, V> implements Serializable {

    /** Serialization version */
    private static final long serialVersionUID = -6096931280583808322L;

    /** Array of all maps in the composite */
    private Map<K, V>[] composite;

    /** Handle mutation operations */
    private MapMutator<K, V> mutator;

    /**
     * Create a new, empty, CompositeMap.
     */
    @SuppressWarnings("unchecked")
    public CompositeMap() {
        this(new Map[] {}, null);
    }

    /**
     * Create a new CompositeMap with two composited Map instances.
     *
     * @param one  the first Map to be composited
     * @param two  the second Map to be composited
     * @throws IllegalArgumentException if there is a key collision
     */
    @SuppressWarnings("unchecked")
    public CompositeMap(final Map<K, V> one, final Map<K, V> two) {
        this(new Map[] { one, two }, null);
    }

    /**
     * Create a new CompositeMap with two composited Map instances.
     *
     * @param one  the first Map to be composited
     * @param two  the second Map to be composited
     * @param mutator  MapMutator to be used for mutation operations
     */
    @SuppressWarnings("unchecked")
    public CompositeMap(final Map<K, V> one, final Map<K, V> two, final MapMutator<K, V> mutator) {
        this(new Map[] { one, two }, mutator);
    }

    /**
     * Create a new CompositeMap which composites all of the Map instances in the
     * argument. It copies the argument array, it does not use it directly.
     *
     * @param composite  the Maps to be composited
     * @throws IllegalArgumentException if there is a key collision
     */
    public CompositeMap(final Map<K, V>... composite) {
        this(composite, null);
    }

    /**
     * Create a new CompositeMap which composites all of the Map instances in the
     * argument. It copies the argument array, it does not use it directly.
     *
     * @param composite  Maps to be composited
     * @param mutator  MapMutator to be used for mutation operations
     */
    @SuppressWarnings("unchecked")
    public CompositeMap(final Map<K, V>[] composite, final MapMutator<K, V> mutator) {
        this.mutator = mutator;
        this.composite = new Map[0];
        for (int i = composite.length - 1; i >= 0; --i) {
            this.addComposited(composite[i]);
        }
    }

    //-----------------------------------------------------------------------
    /**
     * Specify the MapMutator to be used by mutation operations.
     *
     * @param mutator  the MapMutator to be used for mutation delegation
     */
    public void setMutator(final MapMutator<K, V> mutator) {
        this.mutator = mutator;
    }

    /**
     * Add an additional Map to the composite.
     *
     * @param map  the Map to be added to the composite
     * @throws IllegalArgumentException if there is a key collision and there is no
     *         MapMutator set to handle it.
     */
    @SuppressWarnings("unchecked")
    public synchronized void addComposited(final Map<K, V> map) throws IllegalArgumentException {
        for (int i = composite.length - 1; i >= 0; --i) {
            final Collection<K> intersect = CollectionUtils.intersection(this.composite[i].keySet(), map.keySet());
            if (intersect.size() != 0) {
                if (this.mutator == null) {
                    throw new IllegalArgumentException("Key collision adding Map to CompositeMap");
                }
                this.mutator.resolveCollision(this, this.composite[i], map, intersect);
            }
        }
        final Map<K, V>[] temp = new Map[this.composite.length + 1];
        System.arraycopy(this.composite, 0, temp, 0, this.composite.length);
        temp[temp.length - 1] = map;
        this.composite = temp;
    }

    /**
     * Remove a Map from the composite.
     *
     * @param map  the Map to be removed from the composite
     * @return The removed Map or <code>null</code> if map is not in the composite
     */
    @SuppressWarnings("unchecked")
    public synchronized Map<K, V> removeComposited(final Map<K, V> map) {
        final int size = this.composite.length;
        for (int i = 0; i < size; ++i) {
            if (this.composite[i].equals(map)) {
                final Map<K, V>[] temp = new Map[size - 1];
                System.arraycopy(this.composite, 0, temp, 0, i);
                System.arraycopy(this.composite, i + 1, temp, i, size - i - 1);
                this.composite = temp;
                return map;
            }
        }
        return null;
    }

    //-----------------------------------------------------------------------
    /**
     * Calls <code>clear()</code> on all composited Maps.
     *
     * @throws UnsupportedOperationException if any of the composited Maps do not support clear()
     */
    public void clear() {
        for (int i = this.composite.length - 1; i >= 0; --i) {
            this.composite[i].clear();
        }
    }

    /**
     * Returns <tt>true</tt> if this map contains a mapping for the specified
     * key.  More formally, returns <tt>true</tt> if and only if
     * this map contains at a mapping for a key <tt>k</tt> such that
     * <tt>(key==null ? k==null : key.equals(k))</tt>.  (There can be
     * at most one such mapping.)
     *
     * @param key  key whose presence in this map is to be tested.
     * @return <tt>true</tt> if this map contains a mapping for the specified
     *         key.
     *
     * @throws ClassCastException if the key is of an inappropriate type for
     *         this map (optional).
     * @throws NullPointerException if the key is <tt>null</tt> and this map
     *            does not not permit <tt>null</tt> keys (optional).
     */
    public boolean containsKey(final Object key) {
        for (int i = this.composite.length - 1; i >= 0; --i) {
            if (this.composite[i].containsKey(key)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns <tt>true</tt> if this map maps one or more keys to the
     * specified value.  More formally, returns <tt>true</tt> if and only if
     * this map contains at least one mapping to a value <tt>v</tt> such that
     * <tt>(value==null ? v==null : value.equals(v))</tt>.  This operation
     * will probably require time linear in the map size for most
     * implementations of the <tt>Map</tt> interface.
     *
     * @param value value whose presence in this map is to be tested.
     * @return <tt>true</tt> if this map maps one or more keys to the
     *         specified value.
     * @throws ClassCastException if the value is of an inappropriate type for
     *         this map (optional).
     * @throws NullPointerException if the value is <tt>null</tt> and this map
     *            does not not permit <tt>null</tt> values (optional).
     */
    public boolean containsValue(final Object value) {
        for (int i = this.composite.length - 1; i >= 0; --i) {
            if (this.composite[i].containsValue(value)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns a set view of the mappings contained in this map.  Each element
     * in the returned set is a <code>Map.Entry</code>.  The set is backed by the
     * map, so changes to the map are reflected in the set, and vice-versa.
     * If the map is modified while an iteration over the set is in progress,
     * the results of the iteration are undefined.  The set supports element
     * removal, which removes the corresponding mapping from the map, via the
     * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
     * <tt>retainAll</tt> and <tt>clear</tt> operations.  It does not support
     * the <tt>add</tt> or <tt>addAll</tt> operations.
     * <p>
     * This implementation returns a <code>CompositeSet</code> which
     * composites the entry sets from all of the composited maps.
     *
     * @see CompositeSet
     * @return a set view of the mappings contained in this map.
     */
    public Set<Map.Entry<K, V>> entrySet() {
        final CompositeSet<Map.Entry<K, V>> entries = new CompositeSet<Map.Entry<K,V>>();
        for (int i = composite.length - 1; i >= 0; --i) {
            entries.addComposited(composite[i].entrySet());
        }
        return entries;
    }

    /**
     * Returns the value to which this map maps the specified key.  Returns
     * <tt>null</tt> if the map contains no mapping for this key.  A return
     * value of <tt>null</tt> does not <i>necessarily</i> indicate that the
     * map contains no mapping for the key; it's also possible that the map
     * explicitly maps the key to <tt>null</tt>.  The <tt>containsKey</tt>
     * operation may be used to distinguish these two cases.
     *
     * <p>More formally, if this map contains a mapping from a key
     * <tt>k</tt> to a value <tt>v</tt> such that <tt>(key==null ? k==null :
     * key.equals(k))</tt>, then this method returns <tt>v</tt>; otherwise
     * it returns <tt>null</tt>.  (There can be at most one such mapping.)
     *
     * @param key key whose associated value is to be returned.
     * @return the value to which this map maps the specified key, or
     *         <tt>null</tt> if the map contains no mapping for this key.
     *
     * @throws ClassCastException if the key is of an inappropriate type for
     *         this map (optional).
     * @throws NullPointerException key is <tt>null</tt> and this map does not
     *         not permit <tt>null</tt> keys (optional).
     *
     * @see #containsKey(Object)
     */
    public V get(final Object key) {
        for (int i = this.composite.length - 1; i >= 0; --i) {
            if (this.composite[i].containsKey(key)) {
                return this.composite[i].get(key);
            }
        }
        return null;
    }

    /**
     * Returns <tt>true</tt> if this map contains no key-value mappings.
     *
     * @return <tt>true</tt> if this map contains no key-value mappings.
     */
    public boolean isEmpty() {
        for (int i = this.composite.length - 1; i >= 0; --i) {
            if (!this.composite[i].isEmpty()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns a set view of the keys contained in this map.  The set is
     * backed by the map, so changes to the map are reflected in the set, and
     * vice-versa.  If the map is modified while an iteration over the set is
     * in progress, the results of the iteration are undefined.  The set
     * supports element removal, which removes the corresponding mapping from
     * the map, via the <tt>Iterator.remove</tt>, <tt>Set.remove</tt>,
     * <tt>removeAll</tt> <tt>retainAll</tt>, and <tt>clear</tt> operations.
     * It does not support the add or <tt>addAll</tt> operations.
     * <p>
     * This implementation returns a <code>CompositeSet</code> which
     * composites the key sets from all of the composited maps.
     *
     * @return a set view of the keys contained in this map.
     */
    public Set<K> keySet() {
        final CompositeSet<K> keys = new CompositeSet<K>();
        for (int i = this.composite.length - 1; i >= 0; --i) {
            keys.addComposited(this.composite[i].keySet());
        }
        return keys;
    }

    /**
     * Associates the specified value with the specified key in this map
     * (optional operation).  If the map previously contained a mapping for
     * this key, the old value is replaced by the specified value.  (A map
     * <tt>m</tt> is said to contain a mapping for a key <tt>k</tt> if and only
     * if {@link #containsKey(Object) m.containsKey(k)} would return
     * <tt>true</tt>.))
     *
     * @param key key with which the specified value is to be associated.
     * @param value value to be associated with the specified key.
     * @return previous value associated with specified key, or <tt>null</tt>
     *         if there was no mapping for key.  A <tt>null</tt> return can
     *         also indicate that the map previously associated <tt>null</tt>
     *         with the specified key, if the implementation supports
     *         <tt>null</tt> values.
     *
     * @throws UnsupportedOperationException if no MapMutator has been specified
     * @throws ClassCastException if the class of the specified key or value
     *            prevents it from being stored in this map.
     * @throws IllegalArgumentException if some aspect of this key or value
     *            prevents it from being stored in this map.
     * @throws NullPointerException this map does not permit <tt>null</tt>
     *            keys or values, and the specified key or value is
     *            <tt>null</tt>.
     */
    public V put(final K key, final V value) {
        if (this.mutator == null) {
            throw new UnsupportedOperationException("No mutator specified");
        }
        return this.mutator.put(this, this.composite, key, value);
    }

    /**
     * Copies all of the mappings from the specified map to this map
     * (optional operation).  The effect of this call is equivalent to that
     * of calling {@link #put(Object,Object) put(k, v)} on this map once
     * for each mapping from key <tt>k</tt> to value <tt>v</tt> in the
     * specified map.  The behavior of this operation is unspecified if the
     * specified map is modified while the operation is in progress.
     *
     * @param map Mappings to be stored in this map.
     *
     * @throws UnsupportedOperationException if the <tt>putAll</tt> method is
     *         not supported by this map.
     *
     * @throws ClassCastException if the class of a key or value in the
     *         specified map prevents it from being stored in this map.
     *
     * @throws IllegalArgumentException some aspect of a key or value in the
     *         specified map prevents it from being stored in this map.
     * @throws NullPointerException the specified map is <tt>null</tt>, or if
     *         this map does not permit <tt>null</tt> keys or values, and the
     *         specified map contains <tt>null</tt> keys or values.
     */
    public void putAll(final Map<? extends K, ? extends V> map) {
        if (this.mutator == null) {
            throw new UnsupportedOperationException("No mutator specified");
        }
        this.mutator.putAll(this, this.composite, map);
    }

    /**
     * Removes the mapping for this key from this map if it is present
     * (optional operation).   More formally, if this map contains a mapping
     * from key <tt>k</tt> to value <tt>v</tt> such that
     * <code>(key==null ?  k==null : key.equals(k))</code>, that mapping
     * is removed.  (The map can contain at most one such mapping.)
     *
     * <p>Returns the value to which the map previously associated the key, or
     * <tt>null</tt> if the map contained no mapping for this key.  (A
     * <tt>null</tt> return can also indicate that the map previously
     * associated <tt>null</tt> with the specified key if the implementation
     * supports <tt>null</tt> values.)  The map will not contain a mapping for
     * the specified  key once the call returns.
     *
     * @param key key whose mapping is to be removed from the map.
     * @return previous value associated with specified key, or <tt>null</tt>
     *         if there was no mapping for key.
     *
     * @throws ClassCastException if the key is of an inappropriate type for
     *         the composited map (optional).
     * @throws NullPointerException if the key is <tt>null</tt> and the composited map
     *            does not not permit <tt>null</tt> keys (optional).
     * @throws UnsupportedOperationException if the <tt>remove</tt> method is
     *         not supported by the composited map containing the key
     */
    public V remove(final Object key) {
        for (int i = this.composite.length - 1; i >= 0; --i) {
            if (this.composite[i].containsKey(key)) {
                return this.composite[i].remove(key);
            }
        }
        return null;
    }

    /**
     * Returns the number of key-value mappings in this map.  If the
     * map contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
     * <tt>Integer.MAX_VALUE</tt>.
     *
     * @return the number of key-value mappings in this map.
     */
    public int size() {
        int size = 0;
        for (int i = this.composite.length - 1; i >= 0; --i) {
            size += this.composite[i].size();
        }
        return size;
    }

    /**
     * Returns a collection view of the values contained in this map.  The
     * collection is backed by the map, so changes to the map are reflected in
     * the collection, and vice-versa.  If the map is modified while an
     * iteration over the collection is in progress, the results of the
     * iteration are undefined.  The collection supports element removal,
     * which removes the corresponding mapping from the map, via the
     * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
     * <tt>removeAll</tt>, <tt>retainAll</tt> and <tt>clear</tt> operations.
     * It does not support the add or <tt>addAll</tt> operations.
     *
     * @return a collection view of the values contained in this map.
     */
    public Collection<V> values() {
        final CompositeCollection<V> values = new CompositeCollection<V>();
        for (int i = composite.length - 1; i >= 0; --i) {
            values.addComposited(composite[i].values());
        }
        return values;
    }

    /**
     * Checks if this Map equals another as per the Map specification.
     *
     * @param obj  the object to compare to
     * @return true if the maps are equal
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof Map) {
            final Map<?, ?> map = (Map<?, ?>) obj;
            return this.entrySet().equals(map.entrySet());
        }
        return false;
    }

    /**
     * Gets a hash code for the Map as per the Map specification.
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        int code = 0;
        for (final Map.Entry<K, V> entry : entrySet()) {
            code += entry.hashCode();
        }
        return code;
    }

    /**
     * This interface allows definition for all of the indeterminate
     * mutators in a CompositeMap, as well as providing a hook for
     * callbacks on key collisions.
     */
    public static interface MapMutator<K, V> extends Serializable {
        /**
         * Called when adding a new Composited Map results in a
         * key collision.
         *
         * @param composite  the CompositeMap with the collision
         * @param existing  the Map already in the composite which contains the
         *        offending key
         * @param added  the Map being added
         * @param intersect  the intersection of the keysets of the existing and added maps
         */
        void resolveCollision(CompositeMap<K, V> composite, Map<K, V> existing,
                Map<K, V> added, Collection<K> intersect);

        /**
         * Called when the CompositeMap.put() method is invoked.
         *
         * @param map  the CompositeMap which is being modified
         * @param composited  array of Maps in the CompositeMap being modified
         * @param key  key with which the specified value is to be associated.
         * @param value  value to be associated with the specified key.
         * @return previous value associated with specified key, or <tt>null</tt>
         *         if there was no mapping for key.  A <tt>null</tt> return can
         *         also indicate that the map previously associated <tt>null</tt>
         *         with the specified key, if the implementation supports
         *         <tt>null</tt> values.
         *
         * @throws UnsupportedOperationException if not defined
         * @throws ClassCastException if the class of the specified key or value
         *            prevents it from being stored in this map.
         * @throws IllegalArgumentException if some aspect of this key or value
         *            prevents it from being stored in this map.
         * @throws NullPointerException this map does not permit <tt>null</tt>
         *            keys or values, and the specified key or value is
         *            <tt>null</tt>.
         */
        V put(CompositeMap<K, V> map, Map<K, V>[] composited, K key, V value);

        /**
         * Called when the CompositeMap.putAll() method is invoked.
         *
         * @param map  the CompositeMap which is being modified
         * @param composited  array of Maps in the CompositeMap being modified
         * @param mapToAdd  Mappings to be stored in this CompositeMap
         *
         * @throws UnsupportedOperationException if not defined
         * @throws ClassCastException if the class of the specified key or value
         *            prevents it from being stored in this map.
         * @throws IllegalArgumentException if some aspect of this key or value
         *            prevents it from being stored in this map.
         * @throws NullPointerException this map does not permit <tt>null</tt>
         *            keys or values, and the specified key or value is
         *            <tt>null</tt>.
         */
        void putAll(CompositeMap<K, V> map, Map<K, V>[] composited,
                Map<? extends K, ? extends V> mapToAdd);
    }
}