OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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