OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | 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
 
80 ilm 226
    /**
227
     * Returns a {@link Collection} view of all the values contained in this map. The collection is
228
     * backed by the map, so changes to the map are reflected in the collection, and vice-versa. If
229
     * the map is modified while an iteration over the collection is in progress (except through the
230
     * iterator's own <tt>remove</tt> operation), the results of the iteration are undefined. The
231
     * collection supports element removal, which removes the corresponding values from the map, via
232
     * the <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, <tt>removeAll</tt>,
233
     * <tt>retainAll</tt> and <tt>clear</tt> operations. Note that it doesn't remove entries only
234
     * values : keySet() doesn't change, use {@link #removeAllEmptyCollections()} and
235
     * {@link #removeAllNullCollections()} afterwards. It does not support the <tt>add</tt> or
236
     * <tt>addAll</tt> operations.
237
     *
238
     * @return a view all values in all entries, <code>null</code> collections are ignored.
239
     */
83 ilm 240
    @Override
80 ilm 241
    public Collection<V> allValues() {
242
        if (this.allValues == null)
243
            this.allValues = new AllValues();
244
        return this.allValues;
245
    }
246
 
247
    private final class AllValues extends AbstractCollection<V> {
248
        @Override
249
        public Iterator<V> iterator() {
250
            return new AllValuesIterator();
251
        }
252
 
253
        @Override
254
        public int size() {
255
            int compt = 0;
83 ilm 256
            for (final C c : values()) {
257
                if (c != null)
258
                    compt += c.size();
80 ilm 259
            }
260
            return compt;
261
        }
262
 
263
        // don't overload clear() to call Map.clear() as this would be incoherent with removeAll() :
264
        // this last method only removes values, resulting in empty and null collections
265
    }
266
 
267
    private final class AllValuesIterator implements Iterator<V> {
268
        private final Iterator<C> mapIterator;
269
        private Iterator<V> tempIterator;
270
 
271
        private AllValuesIterator() {
272
            this.mapIterator = values().iterator();
273
            this.tempIterator = null;
274
        }
275
 
276
        private boolean searchNextIterator() {
277
            // tempIterator == null initially and when a collection is null
278
            while (this.tempIterator == null || !this.tempIterator.hasNext()) {
279
                if (!this.mapIterator.hasNext()) {
280
                    return false;
281
                }
282
                final C nextCol = this.mapIterator.next();
283
                this.tempIterator = nextCol == null ? null : nextCol.iterator();
284
            }
285
            return true;
286
        }
287
 
288
        @Override
289
        public boolean hasNext() {
290
            return searchNextIterator();
291
        }
292
 
293
        @Override
294
        public V next() {
295
            // search next iterator if necessary
296
            if (!hasNext())
297
                throw new NoSuchElementException();
298
            return this.tempIterator.next();
299
        }
300
 
301
        @Override
302
        public void remove() {
303
            if (this.tempIterator == null)
304
                throw new IllegalStateException();
305
            this.tempIterator.remove();
306
        }
307
    }
308
 
65 ilm 309
    @Override
310
    public Set<Map.Entry<K, C>> entrySet() {
311
        if (getMode() == Mode.NULL_FORBIDDEN) {
83 ilm 312
            // prevent null insertion
65 ilm 313
            // MAYBE cache
314
            return new EntrySet(super.entrySet());
315
        } else {
316
            return super.entrySet();
317
        }
318
    }
319
 
320
    private final class EntrySet extends AbstractCollection<Map.Entry<K, C>> implements Set<Map.Entry<K, C>> {
321
 
322
        private final Set<Map.Entry<K, C>> delegate;
323
 
83 ilm 324
        public EntrySet(final Set<java.util.Map.Entry<K, C>> delegate) {
65 ilm 325
            super();
326
            this.delegate = delegate;
327
        }
328
 
329
        @Override
330
        public int size() {
331
            return this.delegate.size();
332
        }
333
 
334
        @Override
83 ilm 335
        public boolean contains(final Object o) {
65 ilm 336
            return this.delegate.contains(o);
337
        }
338
 
339
        @Override
83 ilm 340
        public boolean remove(final Object o) {
65 ilm 341
            return this.delegate.remove(o);
342
        }
343
 
344
        @Override
345
        public void clear() {
346
            this.delegate.clear();
347
        }
348
 
349
        @Override
350
        public Iterator<Map.Entry<K, C>> iterator() {
351
            return new Iterator<Map.Entry<K, C>>() {
352
 
353
                private final Iterator<Map.Entry<K, C>> delegateIter = EntrySet.this.delegate.iterator();
354
 
355
                @Override
356
                public boolean hasNext() {
357
                    return this.delegateIter.hasNext();
358
                }
359
 
360
                @Override
361
                public Map.Entry<K, C> next() {
362
                    final Map.Entry<K, C> delegate = this.delegateIter.next();
363
                    return new Map.Entry<K, C>() {
364
                        @Override
365
                        public K getKey() {
366
                            return delegate.getKey();
367
                        }
368
 
369
                        @Override
370
                        public C getValue() {
371
                            return delegate.getValue();
372
                        }
373
 
374
                        @Override
83 ilm 375
                        public C setValue(final C value) {
65 ilm 376
                            if (value == null)
377
                                throw new NullPointerException("Putting null collection for " + toStr(getKey()));
378
                            return delegate.setValue(value);
379
                        }
380
                    };
381
                }
382
 
383
                @Override
384
                public void remove() {
385
                    this.delegateIter.remove();
386
                }
387
            };
388
        }
389
 
390
        @Override
83 ilm 391
        public boolean equals(final Object o) {
65 ilm 392
            return this.delegate.equals(o);
393
        }
394
 
395
        @Override
396
        public int hashCode() {
397
            return this.delegate.hashCode();
398
        }
399
 
400
        @Override
83 ilm 401
        public boolean removeAll(final Collection<?> c) {
65 ilm 402
            return this.delegate.removeAll(c);
403
        }
404
    }
405
 
406
    @Override
83 ilm 407
    public final C put(final K key, final C value) {
65 ilm 408
        return this.putCollection(key, value);
409
    }
410
 
411
    // copy passed collection
83 ilm 412
    @Override
413
    public final C putCollection(final K key, final Collection<? extends V> value) {
65 ilm 414
        if (value == null && this.getMode() == Mode.NULL_FORBIDDEN)
415
            throw new NullPointerException("Putting null collection for " + toStr(key));
416
        return super.put(key, value == null ? null : createCollection(value));
417
    }
418
 
144 ilm 419
    @SafeVarargs
83 ilm 420
    public final C putCollection(final K key, final V... value) {
421
        return this.putCollection(key, Arrays.asList(value));
422
    }
423
 
424
    public void putAllCollections(final Map<? extends K, ? extends Collection<? extends V>> m) {
132 ilm 425
        this.putAllCollections(m, false);
426
    }
427
 
428
    public void putAllCollections(final Map<? extends K, ? extends Collection<? extends V>> m, final boolean removeEmptyCollections) {
65 ilm 429
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : m.entrySet()) {
132 ilm 430
            if (!removeEmptyCollections || !e.getValue().isEmpty())
431
                this.putCollection(e.getKey(), e.getValue());
65 ilm 432
        }
433
    }
434
 
435
    // ** add/remove collection
436
 
83 ilm 437
    @Override
438
    public final boolean add(final K k, final V v) {
439
        return this.addAll(k, Collections.singleton(v));
65 ilm 440
    }
441
 
144 ilm 442
    @SafeVarargs
83 ilm 443
    public final boolean addAll(final K k, final V... v) {
444
        return this.addAll(k, Arrays.asList(v));
80 ilm 445
    }
446
 
83 ilm 447
    @Override
448
    public final boolean addAll(final K k, final Collection<? extends V> v) {
65 ilm 449
        final boolean nullIsAll = getMode() == Mode.NULL_MEANS_ALL;
450
        if (v == null && !nullIsAll)
451
            throw new NullPointerException("Adding null collection for " + toStr(k));
83 ilm 452
        final boolean containsKey = this.containsKey(k);
453
        if (v == null) {
454
            return this.putCollection(k, v) != null;
455
        } else if (!containsKey) {
65 ilm 456
            this.putCollection(k, v);
83 ilm 457
            return true;
65 ilm 458
        } else {
459
            final C currentColl = this.get(k);
460
            if (nullIsAll && currentColl == null) {
461
                // ignore since we can't add something to everything
83 ilm 462
                return false;
65 ilm 463
            } else {
464
                // will throw if currentCol is null
83 ilm 465
                return currentColl.addAll(v);
65 ilm 466
            }
467
        }
468
    }
469
 
83 ilm 470
    @Override
471
    public final void merge(final Map<? extends K, ? extends Collection<? extends V>> mm) {
65 ilm 472
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : mm.entrySet()) {
473
            this.addAll(e.getKey(), e.getValue());
474
        }
475
    }
476
 
83 ilm 477
    @Override
478
    public final void mergeScalarMap(final Map<? extends K, ? extends V> scalarMap) {
81 ilm 479
        for (final Map.Entry<? extends K, ? extends V> e : scalarMap.entrySet()) {
480
            this.add(e.getKey(), e.getValue());
481
        }
482
    }
483
 
83 ilm 484
    @Override
144 ilm 485
    public final boolean removeOne(final K k, final V v) {
486
        return this.remove(k, null, v, false, true);
487
    }
488
 
489
    @Override
490
    public final boolean removeAllInstancesOfItem(final K k, final V v) {
83 ilm 491
        return this.removeAll(k, Collections.singleton(v));
80 ilm 492
    }
493
 
83 ilm 494
    @Override
144 ilm 495
    public final boolean removeAll(final K k, final Collection<? extends V> coll) {
496
        return remove(k, coll, null, true, true);
65 ilm 497
    }
498
 
144 ilm 499
    private final boolean remove(final K k, final Collection<? extends V> coll, final V item, final boolean removeAll, final boolean removeEmptyColl) {
500
        assert removeAll && item == null || !removeAll && coll == null : "Non null value ignored";
501
        if (!this.containsKey(k) || removeAll && coll != null && coll.isEmpty())
502
            return false;
65 ilm 503
        boolean removeK = false;
83 ilm 504
        boolean modified = false;
65 ilm 505
        if (getMode() == Mode.NULL_MEANS_ALL) {
144 ilm 506
            if (removeAll && coll == null) {
65 ilm 507
                removeK = true;
144 ilm 508
            } else {
65 ilm 509
                final C currentColl = this.get(k);
510
                if (currentColl == null)
511
                    throw new IllegalStateException("Cannot remove from all for " + toStr(k));
144 ilm 512
                if (removeAll)
513
                    modified = currentColl.removeAll(coll);
514
                else
515
                    modified = currentColl.remove(item);
516
                if (removeEmptyColl && currentColl.isEmpty())
65 ilm 517
                    removeK = true;
518
            }
144 ilm 519
        } else {
65 ilm 520
            final C currentColl = this.get(k);
144 ilm 521
            // like addAll(), will throw if currentCol is null
522
            if (removeAll) {
523
                modified = currentColl.removeAll(coll);
65 ilm 524
            } else {
144 ilm 525
                modified = currentColl.remove(item);
65 ilm 526
            }
144 ilm 527
            if (removeEmptyColl && currentColl.isEmpty())
528
                removeK = true;
65 ilm 529
        }
83 ilm 530
        if (removeK) {
144 ilm 531
            this.remove(k);
532
            // since we just tested containsKey()
533
            modified = true;
83 ilm 534
        }
535
        return modified;
65 ilm 536
    }
537
 
83 ilm 538
    @Override
539
    public final boolean removeAll(final Map<? extends K, ? extends Collection<? extends V>> mm) {
144 ilm 540
        // allow mm.removeAll(mm)
541
        if (this == mm) {
542
            if (this.isEmpty())
543
                return false;
544
            this.clear();
545
            return true;
546
        }
83 ilm 547
        boolean modified = false;
144 ilm 548
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : mm.entrySet()) {
549
            modified |= this.removeAll(e.getKey(), e.getValue());
65 ilm 550
        }
83 ilm 551
        return modified;
65 ilm 552
    }
553
 
83 ilm 554
    @Override
555
    public final boolean removeAllScalar(final Map<? extends K, ? extends V> m) {
556
        boolean modified = false;
81 ilm 557
        // incompatible types, allowing removal without ConcurrentModificationException
558
        assert m != this;
559
        for (final Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
144 ilm 560
            modified |= this.removeAllInstancesOfItem(e.getKey(), e.getValue());
81 ilm 561
        }
83 ilm 562
        return modified;
81 ilm 563
    }
564
 
65 ilm 565
    // ** remove empty/null collections
566
 
83 ilm 567
    public final C removeIfEmpty(final K k) {
65 ilm 568
        final C v = this.get(k);
569
        if (v != null && v.isEmpty())
570
            return this.remove(k);
571
        else
572
            return null;
573
    }
574
 
83 ilm 575
    public final void removeIfNull(final K k) {
65 ilm 576
        if (this.get(k) == null)
577
            this.remove(k);
578
    }
579
 
83 ilm 580
    @Override
80 ilm 581
    public final Set<K> removeAllEmptyCollections() {
582
        return this.removeAll(true);
65 ilm 583
    }
584
 
83 ilm 585
    @Override
80 ilm 586
    public final Set<K> removeAllNullCollections() {
587
        return this.removeAll(false);
65 ilm 588
    }
589
 
80 ilm 590
    private final Set<K> removeAll(final boolean emptyOrNull) {
591
        final Set<K> removed = new HashSet<K>();
65 ilm 592
        final Iterator<Map.Entry<K, C>> iter = this.entrySet().iterator();
593
        while (iter.hasNext()) {
594
            final Map.Entry<K, C> e = iter.next();
595
            final C val = e.getValue();
80 ilm 596
            if ((emptyOrNull && val != null && val.isEmpty()) || (!emptyOrNull && val == null)) {
65 ilm 597
                iter.remove();
80 ilm 598
                removed.add(e.getKey());
599
            }
65 ilm 600
        }
80 ilm 601
        return removed;
65 ilm 602
    }
603
 
81 ilm 604
    public abstract C createCollection(Collection<? extends V> v);
65 ilm 605
 
606
    @Override
607
    public int hashCode() {
83 ilm 608
        if (this.mode == Mode.NULL_MEANS_ALL)
609
            return this.hashCodeExact();
610
        else
611
            return super.hashCode();
612
    }
613
 
614
    public int hashCodeExact() {
65 ilm 615
        final int prime = 31;
616
        int result = super.hashCode();
617
        result = prime * result + (this.emptyCollSameAsNoColl ? 1231 : 1237);
618
        result = prime * result + this.mode.hashCode();
619
        return result;
620
    }
621
 
83 ilm 622
    /**
623
     * Compares the specified object with this map for equality. Except for
624
     * {@link Mode#NULL_MEANS_ALL}, returns <tt>true</tt> if the given object is also a map and the
625
     * two maps represent the same mappings (as required by {@link Map}).
626
     * <code>NULL_MEANS_ALL</code> maps are tested using {@link #equalsExact(Object)}, meaning they
627
     * don't conform to the Map interface.
628
     *
629
     * @param obj object to be compared for equality with this map
630
     * @return <tt>true</tt> if the specified object is equal to this map
631
     * @see #equalsExact(Object)
632
     */
65 ilm 633
    @Override
83 ilm 634
    public final boolean equals(final Object obj) {
635
        return this.equals(obj, false);
636
    }
637
 
638
    /**
639
     * Compares the specified object with this map for complete equality. This method not only
640
     * checks for equality of values (as required by {@link Map}) but also the class and attributes.
641
     *
642
     * @param obj object to be compared for equality with this map
643
     * @return <tt>true</tt> if the specified object is exactly equal to this map.
644
     */
645
    public final boolean equalsExact(final Object obj) {
646
        return this.equals(obj, true);
647
    }
648
 
649
    private final boolean equals(final Object obj, final boolean forceExact) {
65 ilm 650
        if (this == obj)
651
            return true;
652
        if (!super.equals(obj))
653
            return false;
83 ilm 654
        assert obj != null;
655
        final CollectionMap2<?, ?, ?> other = obj instanceof CollectionMap2 ? (CollectionMap2<?, ?, ?>) obj : null;
656
        if (forceExact || this.mode == Mode.NULL_MEANS_ALL || (other != null && other.mode == Mode.NULL_MEANS_ALL)) {
657
            if (getClass() != obj.getClass())
658
                return false;
659
            // no need to test createCollection(), since values are tested by super.equals()
660
            return this.emptyCollSameAsNoColl == other.emptyCollSameAsNoColl && this.mode == other.mode && this.getDelegate().getClass() == other.getDelegate().getClass();
661
        } else {
662
            return true;
663
        }
65 ilm 664
    }
81 ilm 665
 
666
    @Override
83 ilm 667
    public CollectionMap2<K, C, V> clone() throws CloneNotSupportedException {
81 ilm 668
        @SuppressWarnings("unchecked")
669
        final CollectionMap2<K, C, V> result = (CollectionMap2<K, C, V>) super.clone();
670
        // allValues has a reference to this
671
        result.allValues = null;
672
        // clone each collection value
83 ilm 673
        for (final Map.Entry<K, C> entry : result.entrySet()) {
81 ilm 674
            final C coll = entry.getValue();
675
            entry.setValue(createCollection(coll));
676
        }
677
        return result;
678
    }
65 ilm 679
}