OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
65 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.utils;
15
 
83 ilm 16
import org.openconcerto.utils.cc.AbstractMapDecorator;
17
 
65 ilm 18
import java.util.AbstractCollection;
80 ilm 19
import java.util.Arrays;
65 ilm 20
import java.util.Collection;
21
import java.util.Collections;
22
import java.util.HashMap;
80 ilm 23
import java.util.HashSet;
65 ilm 24
import java.util.Iterator;
25
import java.util.Map;
80 ilm 26
import java.util.NoSuchElementException;
65 ilm 27
import java.util.Set;
28
 
29
/**
144 ilm 30
 * Allow to map keys to collections. This map always allow <code>null</code> items inside mapped
31
 * collections, but it may restrict null collections, see {@link Mode}.
65 ilm 32
 *
33
 * @author Sylvain
34
 *
35
 * @param <K> the type of keys maintained by this map
36
 * @param <C> the type of mapped collections
37
 * @param <V> the type of elements of the collections
38
 */
83 ilm 39
public abstract class CollectionMap2<K, C extends Collection<V>, V> extends AbstractMapDecorator<K, C> implements Cloneable, CollectionMap2Itf<K, C, V> {
65 ilm 40
 
41
    static final int DEFAULT_INITIAL_CAPACITY = 16;
42
 
43
    private static final String toStr(final Object o) {
44
        return o == null ? "null" : "'" + o + "'";
45
    }
46
 
47
    public static enum Mode {
48
        /**
49
         * Mapped collections cannot be <code>null</code>.
50
         */
51
        NULL_FORBIDDEN,
52
        /**
53
         * Mapped collections can be <code>null</code>, but some methods may throw
54
         * {@link NullPointerException}.
55
         *
56
         * @see CollectionMap2#addAll(Object, Collection)
57
         * @see CollectionMap2#removeAll(Object, Collection)
58
         */
59
        NULL_ALLOWED,
60
        /**
61
         * Mapped collections can be <code>null</code>, meaning every possible item. Thus no method
62
         * throws {@link NullPointerException}.
63
         */
64
        NULL_MEANS_ALL
65
    }
66
 
83 ilm 67
    static protected final Mode DEFAULT_MODE = Mode.NULL_FORBIDDEN;
68
    static private final Boolean DEFAULT_emptyCollSameAsNoColl = null;
65 ilm 69
 
70
    private final boolean emptyCollSameAsNoColl;
71
    private final Mode mode;
81 ilm 72
    private transient Collection<V> allValues = null;
65 ilm 73
 
74
    public CollectionMap2() {
75
        this(DEFAULT_MODE);
76
    }
77
 
78
    public CollectionMap2(final Mode mode) {
83 ilm 79
        this(mode, DEFAULT_emptyCollSameAsNoColl);
65 ilm 80
    }
81
 
83 ilm 82
    public CollectionMap2(final Map<K, C> delegate, final Mode mode) {
83
        this(delegate, mode, DEFAULT_emptyCollSameAsNoColl);
84
    }
85
 
65 ilm 86
    public CollectionMap2(final Mode mode, final Boolean emptyCollSameAsNoColl) {
87
        this(DEFAULT_INITIAL_CAPACITY, mode, emptyCollSameAsNoColl);
88
    }
89
 
83 ilm 90
    public CollectionMap2(final int initialCapacity) {
91
        this(initialCapacity, DEFAULT_MODE, DEFAULT_emptyCollSameAsNoColl);
65 ilm 92
    }
93
 
83 ilm 94
    public CollectionMap2(final int initialCapacity, final Mode mode, final Boolean emptyCollSameAsNoColl) {
95
        this(new HashMap<K, C>(initialCapacity), mode, emptyCollSameAsNoColl);
96
    }
97
 
98
    /**
99
     * Create a new instance with the passed delegate. The delegate is *not* cleared, this allows to
100
     * decorate an existing Map but it also means that the existing collections might not be the
101
     * exact same type as those returned by {@link #createCollection(Collection)}.
102
     *
103
     * @param delegate the map to use, it must not be modified afterwards.
104
     * @param mode how to handle null values.
105
     * @param emptyCollSameAsNoColl for {@link #getCollection(Object)} : whether the lack of an
106
     *        entry is the same as an entry with an empty collection, can be <code>null</code>.
107
     */
108
    public CollectionMap2(final Map<K, C> delegate, final Mode mode, final Boolean emptyCollSameAsNoColl) {
109
        super(delegate);
110
        if (mode == null)
111
            throw new NullPointerException("Null mode");
65 ilm 112
        this.mode = mode;
113
        this.emptyCollSameAsNoColl = emptyCollSameAsNoColl == null ? mode == Mode.NULL_MEANS_ALL : emptyCollSameAsNoColl;
83 ilm 114
        checkMode();
65 ilm 115
    }
116
 
83 ilm 117
    private final void checkMode() {
118
        assert this.mode != null : "Called too early";
119
        if (this.mode == Mode.NULL_FORBIDDEN && this.containsValue(null))
120
            throw new IllegalArgumentException("Null collection");
121
    }
122
 
123
    // ** copy constructors
124
 
174 ilm 125
    // ATTN getDelegate() is not in CollectionMap2Itf, so if one copies an unmodifiableMap() this
126
    // constructor won't be used and the delegate will be the default HashMap (even if the source
127
    // used a LinkedHashMap).
83 ilm 128
    public CollectionMap2(final CollectionMap2<K, C, ? extends V> m) {
129
        this(CopyUtils.copy(m.getDelegate()), m);
130
    }
131
 
132
    public CollectionMap2(final Map<? extends K, ? extends Collection<? extends V>> m) {
133
        this(new HashMap<K, C>(m.size()), m);
134
    }
135
 
136
    /**
137
     * Create a new instance with the passed delegate and filling it with the passed map.
138
     *
139
     * @param delegate the map to use, it will be cleared and must not be modified afterwards.
140
     * @param m the values to put in this, if it's an instance of {@link CollectionMap2} the
141
     *        {@link #getMode() mode} and {@link #isEmptyCollSameAsNoColl()} will be copied as well.
142
     */
143
    public CollectionMap2(final Map<K, C> delegate, final Map<? extends K, ? extends Collection<? extends V>> m) {
65 ilm 144
        // don't use super(Map) since it doesn't copy the collections
145
        // also its type is more restrictive
83 ilm 146
        super(delegate);
65 ilm 147
        if (m instanceof CollectionMap2) {
148
            final CollectionMap2<?, ?, ?> collM = (CollectionMap2<?, ?, ?>) m;
149
            this.mode = collM.getMode();
150
            this.emptyCollSameAsNoColl = collM.isEmptyCollSameAsNoColl();
151
        } else {
152
            this.mode = DEFAULT_MODE;
153
            this.emptyCollSameAsNoColl = this.mode == Mode.NULL_MEANS_ALL;
154
        }
83 ilm 155
        // delegate might not contain the same instances of collections (i.e. LinkedList vs
156
        // ArrayList)
157
        this.clear();
65 ilm 158
        this.putAllCollections(m);
159
    }
160
 
83 ilm 161
    @Override
65 ilm 162
    public final Mode getMode() {
163
        return this.mode;
164
    }
165
 
83 ilm 166
    @Override
65 ilm 167
    public final boolean isEmptyCollSameAsNoColl() {
168
        return this.emptyCollSameAsNoColl;
169
    }
170
 
83 ilm 171
    public final C getNonNullIfMissing(final Object key) {
65 ilm 172
        return this.get(key, false, true);
173
    }
174
 
83 ilm 175
    @Override
176
    public final C getNonNull(final K key) {
65 ilm 177
        return this.get(key, false, false);
178
    }
179
 
83 ilm 180
    private final C getNonNullColl(final C res) {
65 ilm 181
        return res == null ? this.createCollection(Collections.<V> emptySet()) : res;
182
    }
183
 
83 ilm 184
    /**
185
     * Get the collection mapped to the passed key. Note : <code>get(key, true, true)</code> is
186
     * equivalent to <code>get(key)</code>.
187
     *
188
     * @param key the key whose associated value is to be returned.
189
     * @param nullIfMissing only relevant if the key isn't contained : if <code>true</code>
190
     *        <code>null</code> will be returned, otherwise an empty collection.
191
     * @param nullIfPresent only relevant if the key is mapped to <code>null</code> : if
192
     *        <code>true</code> <code>null</code> will be returned, otherwise an empty collection.
193
     * @return the non {@code null} value to which the specified key is mapped, otherwise
194
     *         {@code null} or empty collection depending on the other parameters.
195
     */
196
    @Override
197
    public final C get(final Object key, final boolean nullIfMissing, final boolean nullIfPresent) {
65 ilm 198
        if (nullIfMissing == nullIfPresent) {
199
            final C res = super.get(key);
200
            if (res != null || nullIfMissing && nullIfPresent) {
201
                return res;
202
            } else {
203
                assert !nullIfMissing && !nullIfPresent;
204
                return getNonNullColl(null);
205
            }
206
        } else if (nullIfMissing) {
207
            assert !nullIfPresent;
208
            if (!this.containsKey(key))
209
                return null;
210
            else
211
                return getNonNullColl(super.get(key));
212
        } else {
213
            assert !nullIfMissing && nullIfPresent;
214
            if (this.containsKey(key))
215
                return super.get(key);
216
            else
217
                return getNonNullColl(null);
218
        }
219
    }
220
 
83 ilm 221
    @Override
222
    public final C getCollection(final Object key) {
65 ilm 223
        return this.get(key, !this.isEmptyCollSameAsNoColl(), true);
224
    }
225
 
177 ilm 226
    @Override
227
    public boolean containsInCollection(K key, V val) throws NullPointerException {
228
        final C c = this.get(key);
229
        if (c != null)
230
            return c.contains(val);
231
        else if (!this.containsKey(key))
232
            return false;
233
        else if (getMode() == Mode.NULL_MEANS_ALL)
234
            return true;
235
        else
236
            throw new NullPointerException("Null value for " + key);
237
    }
238
 
80 ilm 239
    /**
240
     * Returns a {@link Collection} view of all the values contained in this map. The collection is
241
     * backed by the map, so changes to the map are reflected in the collection, and vice-versa. If
242
     * the map is modified while an iteration over the collection is in progress (except through the
243
     * iterator's own <tt>remove</tt> operation), the results of the iteration are undefined. The
244
     * collection supports element removal, which removes the corresponding values from the map, via
245
     * the <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, <tt>removeAll</tt>,
246
     * <tt>retainAll</tt> and <tt>clear</tt> operations. Note that it doesn't remove entries only
247
     * values : keySet() doesn't change, use {@link #removeAllEmptyCollections()} and
248
     * {@link #removeAllNullCollections()} afterwards. It does not support the <tt>add</tt> or
249
     * <tt>addAll</tt> operations.
250
     *
251
     * @return a view all values in all entries, <code>null</code> collections are ignored.
252
     */
83 ilm 253
    @Override
80 ilm 254
    public Collection<V> allValues() {
255
        if (this.allValues == null)
256
            this.allValues = new AllValues();
257
        return this.allValues;
258
    }
259
 
260
    private final class AllValues extends AbstractCollection<V> {
261
        @Override
262
        public Iterator<V> iterator() {
263
            return new AllValuesIterator();
264
        }
265
 
266
        @Override
267
        public int size() {
268
            int compt = 0;
83 ilm 269
            for (final C c : values()) {
270
                if (c != null)
271
                    compt += c.size();
80 ilm 272
            }
273
            return compt;
274
        }
275
 
276
        // don't overload clear() to call Map.clear() as this would be incoherent with removeAll() :
277
        // this last method only removes values, resulting in empty and null collections
278
    }
279
 
280
    private final class AllValuesIterator implements Iterator<V> {
281
        private final Iterator<C> mapIterator;
282
        private Iterator<V> tempIterator;
283
 
284
        private AllValuesIterator() {
285
            this.mapIterator = values().iterator();
286
            this.tempIterator = null;
287
        }
288
 
289
        private boolean searchNextIterator() {
290
            // tempIterator == null initially and when a collection is null
291
            while (this.tempIterator == null || !this.tempIterator.hasNext()) {
292
                if (!this.mapIterator.hasNext()) {
293
                    return false;
294
                }
295
                final C nextCol = this.mapIterator.next();
296
                this.tempIterator = nextCol == null ? null : nextCol.iterator();
297
            }
298
            return true;
299
        }
300
 
301
        @Override
302
        public boolean hasNext() {
303
            return searchNextIterator();
304
        }
305
 
306
        @Override
307
        public V next() {
308
            // search next iterator if necessary
309
            if (!hasNext())
310
                throw new NoSuchElementException();
311
            return this.tempIterator.next();
312
        }
313
 
314
        @Override
315
        public void remove() {
316
            if (this.tempIterator == null)
317
                throw new IllegalStateException();
318
            this.tempIterator.remove();
319
        }
320
    }
321
 
65 ilm 322
    @Override
323
    public Set<Map.Entry<K, C>> entrySet() {
324
        if (getMode() == Mode.NULL_FORBIDDEN) {
83 ilm 325
            // prevent null insertion
65 ilm 326
            // MAYBE cache
327
            return new EntrySet(super.entrySet());
328
        } else {
329
            return super.entrySet();
330
        }
331
    }
332
 
333
    private final class EntrySet extends AbstractCollection<Map.Entry<K, C>> implements Set<Map.Entry<K, C>> {
334
 
335
        private final Set<Map.Entry<K, C>> delegate;
336
 
83 ilm 337
        public EntrySet(final Set<java.util.Map.Entry<K, C>> delegate) {
65 ilm 338
            super();
339
            this.delegate = delegate;
340
        }
341
 
342
        @Override
343
        public int size() {
344
            return this.delegate.size();
345
        }
346
 
347
        @Override
83 ilm 348
        public boolean contains(final Object o) {
65 ilm 349
            return this.delegate.contains(o);
350
        }
351
 
352
        @Override
83 ilm 353
        public boolean remove(final Object o) {
65 ilm 354
            return this.delegate.remove(o);
355
        }
356
 
357
        @Override
358
        public void clear() {
359
            this.delegate.clear();
360
        }
361
 
362
        @Override
363
        public Iterator<Map.Entry<K, C>> iterator() {
364
            return new Iterator<Map.Entry<K, C>>() {
365
 
366
                private final Iterator<Map.Entry<K, C>> delegateIter = EntrySet.this.delegate.iterator();
367
 
368
                @Override
369
                public boolean hasNext() {
370
                    return this.delegateIter.hasNext();
371
                }
372
 
373
                @Override
374
                public Map.Entry<K, C> next() {
375
                    final Map.Entry<K, C> delegate = this.delegateIter.next();
376
                    return new Map.Entry<K, C>() {
377
                        @Override
378
                        public K getKey() {
379
                            return delegate.getKey();
380
                        }
381
 
382
                        @Override
383
                        public C getValue() {
384
                            return delegate.getValue();
385
                        }
386
 
387
                        @Override
83 ilm 388
                        public C setValue(final C value) {
65 ilm 389
                            if (value == null)
390
                                throw new NullPointerException("Putting null collection for " + toStr(getKey()));
391
                            return delegate.setValue(value);
392
                        }
393
                    };
394
                }
395
 
396
                @Override
397
                public void remove() {
398
                    this.delegateIter.remove();
399
                }
400
            };
401
        }
402
 
403
        @Override
83 ilm 404
        public boolean equals(final Object o) {
65 ilm 405
            return this.delegate.equals(o);
406
        }
407
 
408
        @Override
409
        public int hashCode() {
410
            return this.delegate.hashCode();
411
        }
412
 
413
        @Override
83 ilm 414
        public boolean removeAll(final Collection<?> c) {
65 ilm 415
            return this.delegate.removeAll(c);
416
        }
417
    }
418
 
419
    @Override
83 ilm 420
    public final C put(final K key, final C value) {
65 ilm 421
        return this.putCollection(key, value);
422
    }
423
 
424
    // copy passed collection
83 ilm 425
    @Override
426
    public final C putCollection(final K key, final Collection<? extends V> value) {
65 ilm 427
        if (value == null && this.getMode() == Mode.NULL_FORBIDDEN)
428
            throw new NullPointerException("Putting null collection for " + toStr(key));
429
        return super.put(key, value == null ? null : createCollection(value));
430
    }
431
 
144 ilm 432
    @SafeVarargs
83 ilm 433
    public final C putCollection(final K key, final V... value) {
434
        return this.putCollection(key, Arrays.asList(value));
435
    }
436
 
437
    public void putAllCollections(final Map<? extends K, ? extends Collection<? extends V>> m) {
132 ilm 438
        this.putAllCollections(m, false);
439
    }
440
 
441
    public void putAllCollections(final Map<? extends K, ? extends Collection<? extends V>> m, final boolean removeEmptyCollections) {
65 ilm 442
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : m.entrySet()) {
132 ilm 443
            if (!removeEmptyCollections || !e.getValue().isEmpty())
444
                this.putCollection(e.getKey(), e.getValue());
65 ilm 445
        }
446
    }
447
 
448
    // ** add/remove collection
449
 
83 ilm 450
    @Override
451
    public final boolean add(final K k, final V v) {
452
        return this.addAll(k, Collections.singleton(v));
65 ilm 453
    }
454
 
144 ilm 455
    @SafeVarargs
83 ilm 456
    public final boolean addAll(final K k, final V... v) {
457
        return this.addAll(k, Arrays.asList(v));
80 ilm 458
    }
459
 
83 ilm 460
    @Override
461
    public final boolean addAll(final K k, final Collection<? extends V> v) {
65 ilm 462
        final boolean nullIsAll = getMode() == Mode.NULL_MEANS_ALL;
463
        if (v == null && !nullIsAll)
464
            throw new NullPointerException("Adding null collection for " + toStr(k));
83 ilm 465
        final boolean containsKey = this.containsKey(k);
466
        if (v == null) {
467
            return this.putCollection(k, v) != null;
468
        } else if (!containsKey) {
65 ilm 469
            this.putCollection(k, v);
83 ilm 470
            return true;
65 ilm 471
        } else {
472
            final C currentColl = this.get(k);
473
            if (nullIsAll && currentColl == null) {
474
                // ignore since we can't add something to everything
83 ilm 475
                return false;
65 ilm 476
            } else {
477
                // will throw if currentCol is null
83 ilm 478
                return currentColl.addAll(v);
65 ilm 479
            }
480
        }
481
    }
482
 
83 ilm 483
    @Override
484
    public final void merge(final Map<? extends K, ? extends Collection<? extends V>> mm) {
65 ilm 485
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : mm.entrySet()) {
486
            this.addAll(e.getKey(), e.getValue());
487
        }
488
    }
489
 
83 ilm 490
    @Override
491
    public final void mergeScalarMap(final Map<? extends K, ? extends V> scalarMap) {
81 ilm 492
        for (final Map.Entry<? extends K, ? extends V> e : scalarMap.entrySet()) {
493
            this.add(e.getKey(), e.getValue());
494
        }
495
    }
496
 
83 ilm 497
    @Override
144 ilm 498
    public final boolean removeOne(final K k, final V v) {
499
        return this.remove(k, null, v, false, true);
500
    }
501
 
502
    @Override
503
    public final boolean removeAllInstancesOfItem(final K k, final V v) {
83 ilm 504
        return this.removeAll(k, Collections.singleton(v));
80 ilm 505
    }
506
 
83 ilm 507
    @Override
144 ilm 508
    public final boolean removeAll(final K k, final Collection<? extends V> coll) {
509
        return remove(k, coll, null, true, true);
65 ilm 510
    }
511
 
144 ilm 512
    private final boolean remove(final K k, final Collection<? extends V> coll, final V item, final boolean removeAll, final boolean removeEmptyColl) {
513
        assert removeAll && item == null || !removeAll && coll == null : "Non null value ignored";
514
        if (!this.containsKey(k) || removeAll && coll != null && coll.isEmpty())
515
            return false;
65 ilm 516
        boolean removeK = false;
83 ilm 517
        boolean modified = false;
65 ilm 518
        if (getMode() == Mode.NULL_MEANS_ALL) {
144 ilm 519
            if (removeAll && coll == null) {
65 ilm 520
                removeK = true;
144 ilm 521
            } else {
65 ilm 522
                final C currentColl = this.get(k);
523
                if (currentColl == null)
524
                    throw new IllegalStateException("Cannot remove from all for " + toStr(k));
144 ilm 525
                if (removeAll)
526
                    modified = currentColl.removeAll(coll);
527
                else
528
                    modified = currentColl.remove(item);
529
                if (removeEmptyColl && currentColl.isEmpty())
65 ilm 530
                    removeK = true;
531
            }
144 ilm 532
        } else {
65 ilm 533
            final C currentColl = this.get(k);
144 ilm 534
            // like addAll(), will throw if currentCol is null
535
            if (removeAll) {
536
                modified = currentColl.removeAll(coll);
65 ilm 537
            } else {
144 ilm 538
                modified = currentColl.remove(item);
65 ilm 539
            }
144 ilm 540
            if (removeEmptyColl && currentColl.isEmpty())
541
                removeK = true;
65 ilm 542
        }
83 ilm 543
        if (removeK) {
144 ilm 544
            this.remove(k);
545
            // since we just tested containsKey()
546
            modified = true;
83 ilm 547
        }
548
        return modified;
65 ilm 549
    }
550
 
83 ilm 551
    @Override
552
    public final boolean removeAll(final Map<? extends K, ? extends Collection<? extends V>> mm) {
144 ilm 553
        // allow mm.removeAll(mm)
554
        if (this == mm) {
555
            if (this.isEmpty())
556
                return false;
557
            this.clear();
558
            return true;
559
        }
83 ilm 560
        boolean modified = false;
144 ilm 561
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : mm.entrySet()) {
562
            modified |= this.removeAll(e.getKey(), e.getValue());
65 ilm 563
        }
83 ilm 564
        return modified;
65 ilm 565
    }
566
 
83 ilm 567
    @Override
568
    public final boolean removeAllScalar(final Map<? extends K, ? extends V> m) {
569
        boolean modified = false;
81 ilm 570
        // incompatible types, allowing removal without ConcurrentModificationException
571
        assert m != this;
572
        for (final Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
144 ilm 573
            modified |= this.removeAllInstancesOfItem(e.getKey(), e.getValue());
81 ilm 574
        }
83 ilm 575
        return modified;
81 ilm 576
    }
577
 
65 ilm 578
    // ** remove empty/null collections
579
 
83 ilm 580
    public final C removeIfEmpty(final K k) {
65 ilm 581
        final C v = this.get(k);
582
        if (v != null && v.isEmpty())
583
            return this.remove(k);
584
        else
585
            return null;
586
    }
587
 
83 ilm 588
    public final void removeIfNull(final K k) {
65 ilm 589
        if (this.get(k) == null)
590
            this.remove(k);
591
    }
592
 
83 ilm 593
    @Override
80 ilm 594
    public final Set<K> removeAllEmptyCollections() {
595
        return this.removeAll(true);
65 ilm 596
    }
597
 
83 ilm 598
    @Override
80 ilm 599
    public final Set<K> removeAllNullCollections() {
600
        return this.removeAll(false);
65 ilm 601
    }
602
 
80 ilm 603
    private final Set<K> removeAll(final boolean emptyOrNull) {
604
        final Set<K> removed = new HashSet<K>();
65 ilm 605
        final Iterator<Map.Entry<K, C>> iter = this.entrySet().iterator();
606
        while (iter.hasNext()) {
607
            final Map.Entry<K, C> e = iter.next();
608
            final C val = e.getValue();
80 ilm 609
            if ((emptyOrNull && val != null && val.isEmpty()) || (!emptyOrNull && val == null)) {
65 ilm 610
                iter.remove();
80 ilm 611
                removed.add(e.getKey());
612
            }
65 ilm 613
        }
80 ilm 614
        return removed;
65 ilm 615
    }
616
 
81 ilm 617
    public abstract C createCollection(Collection<? extends V> v);
65 ilm 618
 
619
    @Override
620
    public int hashCode() {
83 ilm 621
        if (this.mode == Mode.NULL_MEANS_ALL)
622
            return this.hashCodeExact();
623
        else
624
            return super.hashCode();
625
    }
626
 
627
    public int hashCodeExact() {
65 ilm 628
        final int prime = 31;
629
        int result = super.hashCode();
630
        result = prime * result + (this.emptyCollSameAsNoColl ? 1231 : 1237);
631
        result = prime * result + this.mode.hashCode();
632
        return result;
633
    }
634
 
83 ilm 635
    /**
636
     * Compares the specified object with this map for equality. Except for
637
     * {@link Mode#NULL_MEANS_ALL}, returns <tt>true</tt> if the given object is also a map and the
638
     * two maps represent the same mappings (as required by {@link Map}).
639
     * <code>NULL_MEANS_ALL</code> maps are tested using {@link #equalsExact(Object)}, meaning they
640
     * don't conform to the Map interface.
641
     *
642
     * @param obj object to be compared for equality with this map
643
     * @return <tt>true</tt> if the specified object is equal to this map
644
     * @see #equalsExact(Object)
645
     */
65 ilm 646
    @Override
83 ilm 647
    public final boolean equals(final Object obj) {
648
        return this.equals(obj, false);
649
    }
650
 
651
    /**
652
     * Compares the specified object with this map for complete equality. This method not only
653
     * checks for equality of values (as required by {@link Map}) but also the class and attributes.
654
     *
655
     * @param obj object to be compared for equality with this map
656
     * @return <tt>true</tt> if the specified object is exactly equal to this map.
657
     */
658
    public final boolean equalsExact(final Object obj) {
659
        return this.equals(obj, true);
660
    }
661
 
662
    private final boolean equals(final Object obj, final boolean forceExact) {
65 ilm 663
        if (this == obj)
664
            return true;
665
        if (!super.equals(obj))
666
            return false;
83 ilm 667
        assert obj != null;
668
        final CollectionMap2<?, ?, ?> other = obj instanceof CollectionMap2 ? (CollectionMap2<?, ?, ?>) obj : null;
669
        if (forceExact || this.mode == Mode.NULL_MEANS_ALL || (other != null && other.mode == Mode.NULL_MEANS_ALL)) {
670
            if (getClass() != obj.getClass())
671
                return false;
672
            // no need to test createCollection(), since values are tested by super.equals()
673
            return this.emptyCollSameAsNoColl == other.emptyCollSameAsNoColl && this.mode == other.mode && this.getDelegate().getClass() == other.getDelegate().getClass();
674
        } else {
675
            return true;
676
        }
65 ilm 677
    }
81 ilm 678
 
679
    @Override
177 ilm 680
    public CollectionMap2<K, C, V> clone() {
81 ilm 681
        @SuppressWarnings("unchecked")
682
        final CollectionMap2<K, C, V> result = (CollectionMap2<K, C, V>) super.clone();
683
        // allValues has a reference to this
684
        result.allValues = null;
685
        // clone each collection value
83 ilm 686
        for (final Map.Entry<K, C> entry : result.entrySet()) {
81 ilm 687
            final C coll = entry.getValue();
688
            entry.setValue(createCollection(coll));
689
        }
690
        return result;
691
    }
65 ilm 692
}