OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 81 | Rev 132 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 81 Rev 83
Line 11... Line 11...
11
 * When distributing the software, include this License Header Notice in each file.
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
12
 */
13
 
13
 
14
 package org.openconcerto.utils;
14
 package org.openconcerto.utils;
15
 
15
 
-
 
16
import org.openconcerto.utils.cc.AbstractMapDecorator;
-
 
17
 
16
import java.util.AbstractCollection;
18
import java.util.AbstractCollection;
17
import java.util.Arrays;
19
import java.util.Arrays;
18
import java.util.Collection;
20
import java.util.Collection;
19
import java.util.Collections;
21
import java.util.Collections;
20
import java.util.HashMap;
22
import java.util.HashMap;
Line 32... Line 34...
32
 * 
34
 * 
33
 * @param <K> the type of keys maintained by this map
35
 * @param <K> the type of keys maintained by this map
34
 * @param <C> the type of mapped collections
36
 * @param <C> the type of mapped collections
35
 * @param <V> the type of elements of the collections
37
 * @param <V> the type of elements of the collections
36
 */
38
 */
37
public abstract class CollectionMap2<K, C extends Collection<V>, V> extends HashMap<K, C> {
39
public abstract class CollectionMap2<K, C extends Collection<V>, V> extends AbstractMapDecorator<K, C> implements Cloneable, CollectionMap2Itf<K, C, V> {
38
 
40
 
39
    static final int DEFAULT_INITIAL_CAPACITY = 16;
41
    static final int DEFAULT_INITIAL_CAPACITY = 16;
40
 
42
 
41
    private static final String toStr(final Object o) {
43
    private static final String toStr(final Object o) {
42
        return o == null ? "null" : "'" + o + "'";
44
        return o == null ? "null" : "'" + o + "'";
Line 60... Line 62...
60
         * throws {@link NullPointerException}.
62
         * throws {@link NullPointerException}.
61
         */
63
         */
62
        NULL_MEANS_ALL
64
        NULL_MEANS_ALL
63
    }
65
    }
64
 
66
 
65
    static private final Mode DEFAULT_MODE = Mode.NULL_FORBIDDEN;
67
    static protected final Mode DEFAULT_MODE = Mode.NULL_FORBIDDEN;
-
 
68
    static private final Boolean DEFAULT_emptyCollSameAsNoColl = null;
66
 
69
 
67
    private final boolean emptyCollSameAsNoColl;
70
    private final boolean emptyCollSameAsNoColl;
68
    private final Mode mode;
71
    private final Mode mode;
69
    private transient Collection<V> allValues = null;
72
    private transient Collection<V> allValues = null;
70
 
73
 
71
    public CollectionMap2() {
74
    public CollectionMap2() {
72
        this(DEFAULT_MODE);
75
        this(DEFAULT_MODE);
73
    }
76
    }
74
 
77
 
75
    public CollectionMap2(final Mode mode) {
78
    public CollectionMap2(final Mode mode) {
76
        this(mode, null);
79
        this(mode, DEFAULT_emptyCollSameAsNoColl);
-
 
80
    }
-
 
81
 
-
 
82
    public CollectionMap2(final Map<K, C> delegate, final Mode mode) {
-
 
83
        this(delegate, mode, DEFAULT_emptyCollSameAsNoColl);
77
    }
84
    }
78
 
85
 
79
    public CollectionMap2(final Mode mode, final Boolean emptyCollSameAsNoColl) {
86
    public CollectionMap2(final Mode mode, final Boolean emptyCollSameAsNoColl) {
80
        this(DEFAULT_INITIAL_CAPACITY, mode, emptyCollSameAsNoColl);
87
        this(DEFAULT_INITIAL_CAPACITY, mode, emptyCollSameAsNoColl);
81
    }
88
    }
82
 
89
 
83
    public CollectionMap2(int initialCapacity) {
90
    public CollectionMap2(final int initialCapacity) {
84
        this(initialCapacity, DEFAULT_MODE, null);
91
        this(initialCapacity, DEFAULT_MODE, DEFAULT_emptyCollSameAsNoColl);
-
 
92
    }
-
 
93
 
-
 
94
    public CollectionMap2(final int initialCapacity, final Mode mode, final Boolean emptyCollSameAsNoColl) {
-
 
95
        this(new HashMap<K, C>(initialCapacity), mode, emptyCollSameAsNoColl);
85
    }
96
    }
86
 
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
     */
87
    public CollectionMap2(int initialCapacity, final Mode mode, final Boolean emptyCollSameAsNoColl) {
108
    public CollectionMap2(final Map<K, C> delegate, final Mode mode, final Boolean emptyCollSameAsNoColl) {
88
        super(initialCapacity);
109
        super(delegate);
-
 
110
        if (mode == null)
-
 
111
            throw new NullPointerException("Null mode");
89
        this.mode = mode;
112
        this.mode = mode;
90
        this.emptyCollSameAsNoColl = emptyCollSameAsNoColl == null ? mode == Mode.NULL_MEANS_ALL : emptyCollSameAsNoColl;
113
        this.emptyCollSameAsNoColl = emptyCollSameAsNoColl == null ? mode == Mode.NULL_MEANS_ALL : emptyCollSameAsNoColl;
-
 
114
        checkMode();
91
    }
115
    }
92
 
116
 
-
 
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
 
93
    public CollectionMap2(Map<? extends K, ? extends Collection<? extends V>> m) {
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) {
94
        // don't use super(Map) since it doesn't copy the collections
141
        // don't use super(Map) since it doesn't copy the collections
95
        // also its type is more restrictive
142
        // also its type is more restrictive
96
        super(m.size());
143
        super(delegate);
97
        if (m instanceof CollectionMap2) {
144
        if (m instanceof CollectionMap2) {
98
            final CollectionMap2<?, ?, ?> collM = (CollectionMap2<?, ?, ?>) m;
145
            final CollectionMap2<?, ?, ?> collM = (CollectionMap2<?, ?, ?>) m;
99
            this.mode = collM.getMode();
146
            this.mode = collM.getMode();
100
            this.emptyCollSameAsNoColl = collM.isEmptyCollSameAsNoColl();
147
            this.emptyCollSameAsNoColl = collM.isEmptyCollSameAsNoColl();
101
        } else {
148
        } else {
102
            this.mode = DEFAULT_MODE;
149
            this.mode = DEFAULT_MODE;
103
            this.emptyCollSameAsNoColl = this.mode == Mode.NULL_MEANS_ALL;
150
            this.emptyCollSameAsNoColl = this.mode == Mode.NULL_MEANS_ALL;
104
        }
151
        }
-
 
152
        // delegate might not contain the same instances of collections (i.e. LinkedList vs
-
 
153
        // ArrayList)
-
 
154
        this.clear();
105
        this.putAllCollections(m);
155
        this.putAllCollections(m);
106
    }
156
    }
107
 
157
 
-
 
158
    @Override
108
    public final Mode getMode() {
159
    public final Mode getMode() {
109
        return this.mode;
160
        return this.mode;
110
    }
161
    }
111
 
162
 
-
 
163
    @Override
112
    public final boolean isEmptyCollSameAsNoColl() {
164
    public final boolean isEmptyCollSameAsNoColl() {
113
        return this.emptyCollSameAsNoColl;
165
        return this.emptyCollSameAsNoColl;
114
    }
166
    }
115
 
167
 
116
    public final C getNonNullIfMissing(Object key) {
168
    public final C getNonNullIfMissing(final Object key) {
117
        return this.get(key, false, true);
169
        return this.get(key, false, true);
118
    }
170
    }
119
 
171
 
-
 
172
    @Override
120
    public final C getNonNull(K key) {
173
    public final C getNonNull(final K key) {
121
        return this.get(key, false, false);
174
        return this.get(key, false, false);
122
    }
175
    }
123
 
176
 
124
    private final C getNonNullColl(C res) {
177
    private final C getNonNullColl(final C res) {
125
        return res == null ? this.createCollection(Collections.<V> emptySet()) : res;
178
        return res == null ? this.createCollection(Collections.<V> emptySet()) : res;
126
    }
179
    }
127
 
180
 
-
 
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
128
    public final C get(Object key, final boolean nullIfMissing, final boolean nullIfPresent) {
194
    public final C get(final Object key, final boolean nullIfMissing, final boolean nullIfPresent) {
129
        if (nullIfMissing == nullIfPresent) {
195
        if (nullIfMissing == nullIfPresent) {
130
            final C res = super.get(key);
196
            final C res = super.get(key);
131
            if (res != null || nullIfMissing && nullIfPresent) {
197
            if (res != null || nullIfMissing && nullIfPresent) {
132
                return res;
198
                return res;
133
            } else {
199
            } else {
Line 147... Line 213...
147
            else
213
            else
148
                return getNonNullColl(null);
214
                return getNonNullColl(null);
149
        }
215
        }
150
    }
216
    }
151
 
217
 
-
 
218
    @Override
152
    public final C getCollection(Object key) {
219
    public final C getCollection(final Object key) {
153
        return this.get(key, !this.isEmptyCollSameAsNoColl(), true);
220
        return this.get(key, !this.isEmptyCollSameAsNoColl(), true);
154
    }
221
    }
155
 
222
 
156
    /**
223
    /**
157
     * Returns a {@link Collection} view of all the values contained in this map. The collection is
224
     * Returns a {@link Collection} view of all the values contained in this map. The collection is
Line 165... Line 232...
165
     * {@link #removeAllNullCollections()} afterwards. It does not support the <tt>add</tt> or
232
     * {@link #removeAllNullCollections()} afterwards. It does not support the <tt>add</tt> or
166
     * <tt>addAll</tt> operations.
233
     * <tt>addAll</tt> operations.
167
     * 
234
     * 
168
     * @return a view all values in all entries, <code>null</code> collections are ignored.
235
     * @return a view all values in all entries, <code>null</code> collections are ignored.
169
     */
236
     */
-
 
237
    @Override
170
    public Collection<V> allValues() {
238
    public Collection<V> allValues() {
171
        if (this.allValues == null)
239
        if (this.allValues == null)
172
            this.allValues = new AllValues();
240
            this.allValues = new AllValues();
173
        return this.allValues;
241
        return this.allValues;
174
    }
242
    }
Line 178... Line 246...
178
        public Iterator<V> iterator() {
246
        public Iterator<V> iterator() {
179
            return new AllValuesIterator();
247
            return new AllValuesIterator();
180
        }
248
        }
181
 
249
 
182
        @Override
250
        @Override
183
        public boolean isEmpty() {
-
 
184
            return !iterator().hasNext();
-
 
185
        }
-
 
186
 
-
 
187
        @Override
-
 
188
        public int size() {
251
        public int size() {
189
            int compt = 0;
252
            int compt = 0;
190
            final Iterator<V> it = iterator();
-
 
191
            while (it.hasNext()) {
253
            for (final C c : values()) {
192
                it.next();
254
                if (c != null)
193
                compt++;
255
                    compt += c.size();
194
            }
256
            }
195
            return compt;
257
            return compt;
196
        }
258
        }
197
 
259
 
198
        // don't overload clear() to call Map.clear() as this would be incoherent with removeAll() :
260
        // don't overload clear() to call Map.clear() as this would be incoherent with removeAll() :
Line 242... Line 304...
242
    }
304
    }
243
 
305
 
244
    @Override
306
    @Override
245
    public Set<Map.Entry<K, C>> entrySet() {
307
    public Set<Map.Entry<K, C>> entrySet() {
246
        if (getMode() == Mode.NULL_FORBIDDEN) {
308
        if (getMode() == Mode.NULL_FORBIDDEN) {
-
 
309
            // prevent null insertion
247
            // MAYBE cache
310
            // MAYBE cache
248
            return new EntrySet(super.entrySet());
311
            return new EntrySet(super.entrySet());
249
        } else {
312
        } else {
250
            return super.entrySet();
313
            return super.entrySet();
251
        }
314
        }
Line 253... Line 316...
253
 
316
 
254
    private final class EntrySet extends AbstractCollection<Map.Entry<K, C>> implements Set<Map.Entry<K, C>> {
317
    private final class EntrySet extends AbstractCollection<Map.Entry<K, C>> implements Set<Map.Entry<K, C>> {
255
 
318
 
256
        private final Set<Map.Entry<K, C>> delegate;
319
        private final Set<Map.Entry<K, C>> delegate;
257
 
320
 
258
        public EntrySet(Set<java.util.Map.Entry<K, C>> delegate) {
321
        public EntrySet(final Set<java.util.Map.Entry<K, C>> delegate) {
259
            super();
322
            super();
260
            this.delegate = delegate;
323
            this.delegate = delegate;
261
        }
324
        }
262
 
325
 
263
        @Override
326
        @Override
264
        public int size() {
327
        public int size() {
265
            return this.delegate.size();
328
            return this.delegate.size();
266
        }
329
        }
267
 
330
 
268
        @Override
331
        @Override
269
        public boolean contains(Object o) {
332
        public boolean contains(final Object o) {
270
            return this.delegate.contains(o);
333
            return this.delegate.contains(o);
271
        }
334
        }
272
 
335
 
273
        @Override
336
        @Override
274
        public boolean remove(Object o) {
337
        public boolean remove(final Object o) {
275
            return this.delegate.remove(o);
338
            return this.delegate.remove(o);
276
        }
339
        }
277
 
340
 
278
        @Override
341
        @Override
279
        public void clear() {
342
        public void clear() {
Line 304... Line 367...
304
                        public C getValue() {
367
                        public C getValue() {
305
                            return delegate.getValue();
368
                            return delegate.getValue();
306
                        }
369
                        }
307
 
370
 
308
                        @Override
371
                        @Override
309
                        public C setValue(C value) {
372
                        public C setValue(final C value) {
310
                            if (value == null)
373
                            if (value == null)
311
                                throw new NullPointerException("Putting null collection for " + toStr(getKey()));
374
                                throw new NullPointerException("Putting null collection for " + toStr(getKey()));
312
                            return delegate.setValue(value);
375
                            return delegate.setValue(value);
313
                        }
376
                        }
314
                    };
377
                    };
Line 320... Line 383...
320
                }
383
                }
321
            };
384
            };
322
        }
385
        }
323
 
386
 
324
        @Override
387
        @Override
325
        public boolean equals(Object o) {
388
        public boolean equals(final Object o) {
326
            return this.delegate.equals(o);
389
            return this.delegate.equals(o);
327
        }
390
        }
328
 
391
 
329
        @Override
392
        @Override
330
        public int hashCode() {
393
        public int hashCode() {
331
            return this.delegate.hashCode();
394
            return this.delegate.hashCode();
332
        }
395
        }
333
 
396
 
334
        @Override
397
        @Override
335
        public boolean removeAll(Collection<?> c) {
398
        public boolean removeAll(final Collection<?> c) {
336
            return this.delegate.removeAll(c);
399
            return this.delegate.removeAll(c);
337
        }
400
        }
338
    }
401
    }
339
 
402
 
340
    @Override
403
    @Override
341
    public final C put(K key, C value) {
404
    public final C put(final K key, final C value) {
342
        return this.putCollection(key, value);
405
        return this.putCollection(key, value);
343
    }
406
    }
344
 
407
 
345
    // copy passed collection
408
    // copy passed collection
-
 
409
    @Override
346
    public final C putCollection(K key, Collection<? extends V> value) {
410
    public final C putCollection(final K key, final Collection<? extends V> value) {
347
        if (value == null && this.getMode() == Mode.NULL_FORBIDDEN)
411
        if (value == null && this.getMode() == Mode.NULL_FORBIDDEN)
348
            throw new NullPointerException("Putting null collection for " + toStr(key));
412
            throw new NullPointerException("Putting null collection for " + toStr(key));
349
        return super.put(key, value == null ? null : createCollection(value));
413
        return super.put(key, value == null ? null : createCollection(value));
350
    }
414
    }
351
 
415
 
-
 
416
    public final C putCollection(final K key, final V... value) {
-
 
417
        return this.putCollection(key, Arrays.asList(value));
-
 
418
    }
-
 
419
 
352
    public void putAllCollections(Map<? extends K, ? extends Collection<? extends V>> m) {
420
    public void putAllCollections(final Map<? extends K, ? extends Collection<? extends V>> m) {
353
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : m.entrySet()) {
421
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : m.entrySet()) {
354
            this.putCollection(e.getKey(), e.getValue());
422
            this.putCollection(e.getKey(), e.getValue());
355
        }
423
        }
356
    }
424
    }
357
 
425
 
358
    // ** add/remove collection
426
    // ** add/remove collection
359
 
427
 
-
 
428
    @Override
360
    public final void add(K k, V v) {
429
    public final boolean add(final K k, final V v) {
361
        this.addAll(k, Collections.singleton(v));
430
        return this.addAll(k, Collections.singleton(v));
362
    }
431
    }
363
 
432
 
364
    public final void addAll(K k, V... v) {
433
    public final boolean addAll(final K k, final V... v) {
365
        this.addAll(k, Arrays.asList(v));
434
        return this.addAll(k, Arrays.asList(v));
366
    }
435
    }
367
 
436
 
-
 
437
    @Override
368
    public final void addAll(K k, Collection<? extends V> v) {
438
    public final boolean addAll(final K k, final Collection<? extends V> v) {
369
        final boolean nullIsAll = getMode() == Mode.NULL_MEANS_ALL;
439
        final boolean nullIsAll = getMode() == Mode.NULL_MEANS_ALL;
370
        if (v == null && !nullIsAll)
440
        if (v == null && !nullIsAll)
371
            throw new NullPointerException("Adding null collection for " + toStr(k));
441
            throw new NullPointerException("Adding null collection for " + toStr(k));
372
        if (v == null || !this.containsKey(k)) {
442
        final boolean containsKey = this.containsKey(k);
-
 
443
        if (v == null) {
-
 
444
            return this.putCollection(k, v) != null;
-
 
445
        } else if (!containsKey) {
373
            this.putCollection(k, v);
446
            this.putCollection(k, v);
-
 
447
            return true;
374
        } else {
448
        } else {
375
            final C currentColl = this.get(k);
449
            final C currentColl = this.get(k);
376
            if (nullIsAll && currentColl == null) {
450
            if (nullIsAll && currentColl == null) {
377
                // ignore since we can't add something to everything
451
                // ignore since we can't add something to everything
-
 
452
                return false;
378
            } else {
453
            } else {
379
                // will throw if currentCol is null
454
                // will throw if currentCol is null
380
                currentColl.addAll(v);
455
                return currentColl.addAll(v);
381
            }
456
            }
382
        }
457
        }
383
    }
458
    }
384
 
459
 
-
 
460
    @Override
385
    public final void merge(Map<? extends K, ? extends Collection<? extends V>> mm) {
461
    public final void merge(final Map<? extends K, ? extends Collection<? extends V>> mm) {
386
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : mm.entrySet()) {
462
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : mm.entrySet()) {
387
            this.addAll(e.getKey(), e.getValue());
463
            this.addAll(e.getKey(), e.getValue());
388
        }
464
        }
389
    }
465
    }
390
 
466
 
-
 
467
    @Override
391
    public final void mergeScalarMap(Map<? extends K, ? extends V> scalarMap) {
468
    public final void mergeScalarMap(final Map<? extends K, ? extends V> scalarMap) {
392
        for (final Map.Entry<? extends K, ? extends V> e : scalarMap.entrySet()) {
469
        for (final Map.Entry<? extends K, ? extends V> e : scalarMap.entrySet()) {
393
            this.add(e.getKey(), e.getValue());
470
            this.add(e.getKey(), e.getValue());
394
        }
471
        }
395
    }
472
    }
396
 
473
 
-
 
474
    @Override
397
    public final void remove(K k, V v) {
475
    public final boolean remove(final K k, final V v) {
398
        this.removeAll(k, Collections.singleton(v));
476
        return this.removeAll(k, Collections.singleton(v));
399
    }
477
    }
400
 
478
 
-
 
479
    @Override
401
    public final void removeAll(K k, Collection<? extends V> v) {
480
    public final boolean removeAll(final K k, final Collection<? extends V> v) {
402
        this.removeAll(k, v, null);
481
        return this.removeAll(k, v, null);
403
    }
482
    }
404
 
483
 
405
    private final void removeAll(K k, Collection<? extends V> v, final Iterator<Map.Entry<K, C>> iter) {
484
    private final boolean removeAll(final K k, final Collection<? extends V> v, final Iterator<Map.Entry<K, C>> iter) {
406
        boolean removeK = false;
485
        boolean removeK = false;
-
 
486
        boolean modified = false;
407
        if (getMode() == Mode.NULL_MEANS_ALL) {
487
        if (getMode() == Mode.NULL_MEANS_ALL) {
408
            if (v == null) {
488
            if (v == null) {
409
                removeK = true;
489
                removeK = true;
410
            } else if (v.size() > 0) {
490
            } else if (v.size() > 0) {
411
                final C currentColl = this.get(k);
491
                final C currentColl = this.get(k);
412
                if (currentColl == null)
492
                if (currentColl == null)
413
                    throw new IllegalStateException("Cannot remove from all for " + toStr(k));
493
                    throw new IllegalStateException("Cannot remove from all for " + toStr(k));
414
                currentColl.removeAll(v);
494
                modified = currentColl.removeAll(v);
415
                if (currentColl.isEmpty())
495
                if (currentColl.isEmpty())
416
                    removeK = true;
496
                    removeK = true;
417
            }
497
            }
418
        } else if (this.containsKey(k)) {
498
        } else if (this.containsKey(k)) {
419
            final C currentColl = this.get(k);
499
            final C currentColl = this.get(k);
420
            if (currentColl == null && v == null) {
500
            if (currentColl == null && v == null) {
421
                // since containsKey() and coll == null
501
                // since containsKey() and coll == null
422
                assert getMode() == Mode.NULL_ALLOWED;
502
                assert getMode() == Mode.NULL_ALLOWED;
423
                removeK = true;
503
                removeK = true;
-
 
504
                // since we just tested containsKey()
-
 
505
                modified = true;
424
            } else {
506
            } else {
425
                if (v == null)
507
                if (v == null)
426
                    throw new NullPointerException("Removing null collection for " + toStr(k));
508
                    throw new NullPointerException("Removing null collection for " + toStr(k));
427
                currentColl.removeAll(v);
509
                modified = currentColl.removeAll(v);
428
                if (currentColl.isEmpty())
510
                if (currentColl.isEmpty())
429
                    removeK = true;
511
                    removeK = true;
430
            }
512
            }
431
        }
513
        }
432
        if (removeK)
514
        if (removeK) {
433
            if (iter == null)
515
            if (iter == null) {
-
 
516
                modified |= this.containsKey(k);
434
                this.remove(k);
517
                this.remove(k);
435
            else
518
            } else {
436
                iter.remove();
519
                iter.remove();
-
 
520
                modified = true;
-
 
521
            }
-
 
522
        }
-
 
523
        return modified;
437
    }
524
    }
438
 
525
 
-
 
526
    @Override
439
    public final void removeAll(Map<? extends K, ? extends Collection<? extends V>> mm) {
527
    public final boolean removeAll(final Map<? extends K, ? extends Collection<? extends V>> mm) {
-
 
528
        boolean modified = false;
440
        // iterate on this to allow mm.removeAll(mm)
529
        // iterate on this to allow mm.removeAll(mm)
441
        final Iterator<Map.Entry<K, C>> iter = this.entrySet().iterator();
530
        final Iterator<Map.Entry<K, C>> iter = this.entrySet().iterator();
442
        while (iter.hasNext()) {
531
        while (iter.hasNext()) {
443
            final Map.Entry<K, C> e = iter.next();
532
            final Map.Entry<K, C> e = iter.next();
444
            final K key = e.getKey();
533
            final K key = e.getKey();
445
            if (mm.containsKey(key))
534
            if (mm.containsKey(key))
446
                this.removeAll(key, mm.get(key), iter);
535
                modified |= this.removeAll(key, mm.get(key), iter);
447
        }
536
        }
-
 
537
        return modified;
448
    }
538
    }
449
 
539
 
-
 
540
    @Override
450
    public final void removeAllScalar(Map<? extends K, ? extends V> m) {
541
    public final boolean removeAllScalar(final Map<? extends K, ? extends V> m) {
-
 
542
        boolean modified = false;
451
        // incompatible types, allowing removal without ConcurrentModificationException
543
        // incompatible types, allowing removal without ConcurrentModificationException
452
        assert m != this;
544
        assert m != this;
453
        for (final Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
545
        for (final Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
454
            this.remove(e.getKey(), e.getValue());
546
            modified |= this.remove(e.getKey(), e.getValue());
455
        }
547
        }
-
 
548
        return modified;
456
    }
549
    }
457
 
550
 
458
    // ** remove empty/null collections
551
    // ** remove empty/null collections
459
 
552
 
460
    public final C removeIfEmpty(K k) {
553
    public final C removeIfEmpty(final K k) {
461
        final C v = this.get(k);
554
        final C v = this.get(k);
462
        if (v != null && v.isEmpty())
555
        if (v != null && v.isEmpty())
463
            return this.remove(k);
556
            return this.remove(k);
464
        else
557
        else
465
            return null;
558
            return null;
466
    }
559
    }
467
 
560
 
468
    public final void removeIfNull(K k) {
561
    public final void removeIfNull(final K k) {
469
        if (this.get(k) == null)
562
        if (this.get(k) == null)
470
            this.remove(k);
563
            this.remove(k);
471
    }
564
    }
472
 
565
 
-
 
566
    @Override
473
    public final Set<K> removeAllEmptyCollections() {
567
    public final Set<K> removeAllEmptyCollections() {
474
        return this.removeAll(true);
568
        return this.removeAll(true);
475
    }
569
    }
476
 
570
 
-
 
571
    @Override
477
    public final Set<K> removeAllNullCollections() {
572
    public final Set<K> removeAllNullCollections() {
478
        return this.removeAll(false);
573
        return this.removeAll(false);
479
    }
574
    }
480
 
575
 
481
    private final Set<K> removeAll(final boolean emptyOrNull) {
576
    private final Set<K> removeAll(final boolean emptyOrNull) {
Line 494... Line 589...
494
 
589
 
495
    public abstract C createCollection(Collection<? extends V> v);
590
    public abstract C createCollection(Collection<? extends V> v);
496
 
591
 
497
    @Override
592
    @Override
498
    public int hashCode() {
593
    public int hashCode() {
-
 
594
        if (this.mode == Mode.NULL_MEANS_ALL)
-
 
595
            return this.hashCodeExact();
-
 
596
        else
-
 
597
            return super.hashCode();
-
 
598
    }
-
 
599
 
-
 
600
    public int hashCodeExact() {
499
        final int prime = 31;
601
        final int prime = 31;
500
        int result = super.hashCode();
602
        int result = super.hashCode();
501
        result = prime * result + (this.emptyCollSameAsNoColl ? 1231 : 1237);
603
        result = prime * result + (this.emptyCollSameAsNoColl ? 1231 : 1237);
502
        result = prime * result + this.mode.hashCode();
604
        result = prime * result + this.mode.hashCode();
503
        return result;
605
        return result;
504
    }
606
    }
505
 
607
 
-
 
608
    /**
-
 
609
     * Compares the specified object with this map for equality. Except for
-
 
610
     * {@link Mode#NULL_MEANS_ALL}, returns <tt>true</tt> if the given object is also a map and the
-
 
611
     * two maps represent the same mappings (as required by {@link Map}).
-
 
612
     * <code>NULL_MEANS_ALL</code> maps are tested using {@link #equalsExact(Object)}, meaning they
-
 
613
     * don't conform to the Map interface.
-
 
614
     * 
-
 
615
     * @param obj object to be compared for equality with this map
-
 
616
     * @return <tt>true</tt> if the specified object is equal to this map
-
 
617
     * @see #equalsExact(Object)
-
 
618
     */
506
    @Override
619
    @Override
507
    public boolean equals(Object obj) {
620
    public final boolean equals(final Object obj) {
-
 
621
        return this.equals(obj, false);
-
 
622
    }
-
 
623
 
-
 
624
    /**
-
 
625
     * Compares the specified object with this map for complete equality. This method not only
-
 
626
     * checks for equality of values (as required by {@link Map}) but also the class and attributes.
-
 
627
     * 
-
 
628
     * @param obj object to be compared for equality with this map
-
 
629
     * @return <tt>true</tt> if the specified object is exactly equal to this map.
-
 
630
     */
-
 
631
    public final boolean equalsExact(final Object obj) {
-
 
632
        return this.equals(obj, true);
-
 
633
    }
-
 
634
 
-
 
635
    private final boolean equals(final Object obj, final boolean forceExact) {
508
        if (this == obj)
636
        if (this == obj)
509
            return true;
637
            return true;
510
        if (!super.equals(obj))
638
        if (!super.equals(obj))
511
            return false;
639
            return false;
-
 
640
        assert obj != null;
-
 
641
        final CollectionMap2<?, ?, ?> other = obj instanceof CollectionMap2 ? (CollectionMap2<?, ?, ?>) obj : null;
-
 
642
        if (forceExact || this.mode == Mode.NULL_MEANS_ALL || (other != null && other.mode == Mode.NULL_MEANS_ALL)) {
512
        if (getClass() != obj.getClass())
643
            if (getClass() != obj.getClass())
513
            return false;
644
                return false;
514
        // no need to test createCollection(), since values are tested by super.equals()
645
            // no need to test createCollection(), since values are tested by super.equals()
515
        final CollectionMap2<?, ?, ?> other = (CollectionMap2<?, ?, ?>) obj;
-
 
516
        return this.emptyCollSameAsNoColl == other.emptyCollSameAsNoColl && this.mode == other.mode;
646
            return this.emptyCollSameAsNoColl == other.emptyCollSameAsNoColl && this.mode == other.mode && this.getDelegate().getClass() == other.getDelegate().getClass();
-
 
647
        } else {
-
 
648
            return true;
-
 
649
        }
517
    }
650
    }
518
 
651
 
519
    @Override
652
    @Override
520
    public CollectionMap2<K, C, V> clone() {
653
    public CollectionMap2<K, C, V> clone() throws CloneNotSupportedException {
521
        @SuppressWarnings("unchecked")
654
        @SuppressWarnings("unchecked")
522
        final CollectionMap2<K, C, V> result = (CollectionMap2<K, C, V>) super.clone();
655
        final CollectionMap2<K, C, V> result = (CollectionMap2<K, C, V>) super.clone();
523
        // allValues has a reference to this
656
        // allValues has a reference to this
524
        result.allValues = null;
657
        result.allValues = null;
525
        // clone each collection value
658
        // clone each collection value
526
        for (Map.Entry<K, C> entry : result.entrySet()) {
659
        for (final Map.Entry<K, C> entry : result.entrySet()) {
527
            final C coll = entry.getValue();
660
            final C coll = entry.getValue();
528
            entry.setValue(createCollection(coll));
661
            entry.setValue(createCollection(coll));
529
        }
662
        }
530
        return result;
663
        return result;
531
    }
664
    }