OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Go to most recent revision | Details | 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
 
16
import java.util.AbstractCollection;
17
import java.util.Collection;
18
import java.util.Collections;
19
import java.util.HashMap;
20
import java.util.Iterator;
21
import java.util.Map;
22
import java.util.Set;
23
 
24
/**
25
 * Allow to map keys to collections. This map always allow <code>null</code> items for mapped
26
 * collections, but it may restrict null collections.
27
 *
28
 * @author Sylvain
29
 *
30
 * @param <K> the type of keys maintained by this map
31
 * @param <C> the type of mapped collections
32
 * @param <V> the type of elements of the collections
33
 */
34
public abstract class CollectionMap2<K, C extends Collection<V>, V> extends HashMap<K, C> {
35
 
36
    static final int DEFAULT_INITIAL_CAPACITY = 16;
37
 
38
    private static final String toStr(final Object o) {
39
        return o == null ? "null" : "'" + o + "'";
40
    }
41
 
42
    public static enum Mode {
43
        /**
44
         * Mapped collections cannot be <code>null</code>.
45
         */
46
        NULL_FORBIDDEN,
47
        /**
48
         * Mapped collections can be <code>null</code>, but some methods may throw
49
         * {@link NullPointerException}.
50
         *
51
         * @see CollectionMap2#addAll(Object, Collection)
52
         * @see CollectionMap2#removeAll(Object, Collection)
53
         */
54
        NULL_ALLOWED,
55
        /**
56
         * Mapped collections can be <code>null</code>, meaning every possible item. Thus no method
57
         * throws {@link NullPointerException}.
58
         */
59
        NULL_MEANS_ALL
60
    }
61
 
62
    static private final Mode DEFAULT_MODE = Mode.NULL_FORBIDDEN;
63
 
64
    private final boolean emptyCollSameAsNoColl;
65
    private final Mode mode;
66
 
67
    public CollectionMap2() {
68
        this(DEFAULT_MODE);
69
    }
70
 
71
    public CollectionMap2(final Mode mode) {
72
        this(mode, null);
73
    }
74
 
75
    public CollectionMap2(final Mode mode, final Boolean emptyCollSameAsNoColl) {
76
        this(DEFAULT_INITIAL_CAPACITY, mode, emptyCollSameAsNoColl);
77
    }
78
 
79
    public CollectionMap2(int initialCapacity) {
80
        this(initialCapacity, DEFAULT_MODE, null);
81
    }
82
 
83
    public CollectionMap2(int initialCapacity, final Mode mode, final Boolean emptyCollSameAsNoColl) {
84
        super(initialCapacity);
85
        this.mode = mode;
86
        this.emptyCollSameAsNoColl = emptyCollSameAsNoColl == null ? mode == Mode.NULL_MEANS_ALL : emptyCollSameAsNoColl;
87
    }
88
 
89
    public CollectionMap2(Map<? extends K, ? extends Collection<? extends V>> m) {
90
        // don't use super(Map) since it doesn't copy the collections
91
        // also its type is more restrictive
92
        super(m.size());
93
        if (m instanceof CollectionMap2) {
94
            final CollectionMap2<?, ?, ?> collM = (CollectionMap2<?, ?, ?>) m;
95
            this.mode = collM.getMode();
96
            this.emptyCollSameAsNoColl = collM.isEmptyCollSameAsNoColl();
97
        } else {
98
            this.mode = DEFAULT_MODE;
99
            this.emptyCollSameAsNoColl = this.mode == Mode.NULL_MEANS_ALL;
100
        }
101
        this.putAllCollections(m);
102
    }
103
 
104
    public final Mode getMode() {
105
        return this.mode;
106
    }
107
 
108
    public final boolean isEmptyCollSameAsNoColl() {
109
        return this.emptyCollSameAsNoColl;
110
    }
111
 
112
    public final C getNonNullIfMissing(Object key) {
113
        return this.get(key, false, true);
114
    }
115
 
116
    public final C getNonNull(K key) {
117
        return this.get(key, false, false);
118
    }
119
 
120
    private final C getNonNullColl(C res) {
121
        return res == null ? this.createCollection(Collections.<V> emptySet()) : res;
122
    }
123
 
124
    public final C get(Object key, final boolean nullIfMissing, final boolean nullIfPresent) {
125
        if (nullIfMissing == nullIfPresent) {
126
            final C res = super.get(key);
127
            if (res != null || nullIfMissing && nullIfPresent) {
128
                return res;
129
            } else {
130
                assert !nullIfMissing && !nullIfPresent;
131
                return getNonNullColl(null);
132
            }
133
        } else if (nullIfMissing) {
134
            assert !nullIfPresent;
135
            if (!this.containsKey(key))
136
                return null;
137
            else
138
                return getNonNullColl(super.get(key));
139
        } else {
140
            assert !nullIfMissing && nullIfPresent;
141
            if (this.containsKey(key))
142
                return super.get(key);
143
            else
144
                return getNonNullColl(null);
145
        }
146
    }
147
 
148
    public final C getCollection(Object key) {
149
        return this.get(key, !this.isEmptyCollSameAsNoColl(), true);
150
    }
151
 
152
    @Override
153
    public Set<Map.Entry<K, C>> entrySet() {
154
        if (getMode() == Mode.NULL_FORBIDDEN) {
155
            // MAYBE cache
156
            return new EntrySet(super.entrySet());
157
        } else {
158
            return super.entrySet();
159
        }
160
    }
161
 
162
    private final class EntrySet extends AbstractCollection<Map.Entry<K, C>> implements Set<Map.Entry<K, C>> {
163
 
164
        private final Set<Map.Entry<K, C>> delegate;
165
 
166
        public EntrySet(Set<java.util.Map.Entry<K, C>> delegate) {
167
            super();
168
            this.delegate = delegate;
169
        }
170
 
171
        @Override
172
        public int size() {
173
            return this.delegate.size();
174
        }
175
 
176
        @Override
177
        public boolean contains(Object o) {
178
            return this.delegate.contains(o);
179
        }
180
 
181
        @Override
182
        public boolean remove(Object o) {
183
            return this.delegate.remove(o);
184
        }
185
 
186
        @Override
187
        public void clear() {
188
            this.delegate.clear();
189
        }
190
 
191
        @Override
192
        public Iterator<Map.Entry<K, C>> iterator() {
193
            return new Iterator<Map.Entry<K, C>>() {
194
 
195
                private final Iterator<Map.Entry<K, C>> delegateIter = EntrySet.this.delegate.iterator();
196
 
197
                @Override
198
                public boolean hasNext() {
199
                    return this.delegateIter.hasNext();
200
                }
201
 
202
                @Override
203
                public Map.Entry<K, C> next() {
204
                    final Map.Entry<K, C> delegate = this.delegateIter.next();
205
                    return new Map.Entry<K, C>() {
206
                        @Override
207
                        public K getKey() {
208
                            return delegate.getKey();
209
                        }
210
 
211
                        @Override
212
                        public C getValue() {
213
                            return delegate.getValue();
214
                        }
215
 
216
                        @Override
217
                        public C setValue(C value) {
218
                            if (value == null)
219
                                throw new NullPointerException("Putting null collection for " + toStr(getKey()));
220
                            return delegate.setValue(value);
221
                        }
222
                    };
223
                }
224
 
225
                @Override
226
                public void remove() {
227
                    this.delegateIter.remove();
228
                }
229
            };
230
        }
231
 
232
        @Override
233
        public boolean equals(Object o) {
234
            return this.delegate.equals(o);
235
        }
236
 
237
        @Override
238
        public int hashCode() {
239
            return this.delegate.hashCode();
240
        }
241
 
242
        @Override
243
        public boolean removeAll(Collection<?> c) {
244
            return this.delegate.removeAll(c);
245
        }
246
    }
247
 
248
    @Override
249
    public final C put(K key, C value) {
250
        return this.putCollection(key, value);
251
    }
252
 
253
    // copy passed collection
254
    public final C putCollection(K key, Collection<? extends V> value) {
255
        if (value == null && this.getMode() == Mode.NULL_FORBIDDEN)
256
            throw new NullPointerException("Putting null collection for " + toStr(key));
257
        return super.put(key, value == null ? null : createCollection(value));
258
    }
259
 
260
    public void putAllCollections(Map<? extends K, ? extends Collection<? extends V>> m) {
261
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : m.entrySet()) {
262
            this.putCollection(e.getKey(), e.getValue());
263
        }
264
    }
265
 
266
    // ** add/remove collection
267
 
268
    public final void add(K k, V v) {
269
        this.addAll(k, Collections.singleton(v));
270
    }
271
 
272
    public final void addAll(K k, Collection<? extends V> v) {
273
        final boolean nullIsAll = getMode() == Mode.NULL_MEANS_ALL;
274
        if (v == null && !nullIsAll)
275
            throw new NullPointerException("Adding null collection for " + toStr(k));
276
        if (v == null || !this.containsKey(k)) {
277
            this.putCollection(k, v);
278
        } else {
279
            final C currentColl = this.get(k);
280
            if (nullIsAll && currentColl == null) {
281
                // ignore since we can't add something to everything
282
            } else {
283
                // will throw if currentCol is null
284
                currentColl.addAll(v);
285
            }
286
        }
287
    }
288
 
289
    public final void addAll(Map<? extends K, ? extends Collection<? extends V>> mm) {
290
        for (final Map.Entry<? extends K, ? extends Collection<? extends V>> e : mm.entrySet()) {
291
            this.addAll(e.getKey(), e.getValue());
292
        }
293
    }
294
 
295
    public final void removeAll(K k, Collection<? extends V> v) {
296
        this.removeAll(k, v, null);
297
    }
298
 
299
    private final void removeAll(K k, Collection<? extends V> v, final Iterator<Map.Entry<K, C>> iter) {
300
        boolean removeK = false;
301
        if (getMode() == Mode.NULL_MEANS_ALL) {
302
            if (v == null) {
303
                removeK = true;
304
            } else if (v.size() > 0) {
305
                final C currentColl = this.get(k);
306
                if (currentColl == null)
307
                    throw new IllegalStateException("Cannot remove from all for " + toStr(k));
308
                currentColl.removeAll(v);
309
                if (currentColl.isEmpty())
310
                    removeK = true;
311
            }
312
        } else if (this.containsKey(k)) {
313
            final C currentColl = this.get(k);
314
            if (currentColl == null && v == null) {
315
                // since containsKey() and coll == null
316
                assert getMode() == Mode.NULL_ALLOWED;
317
                removeK = true;
318
            } else {
319
                if (v == null)
320
                    throw new NullPointerException("Removing null collection for " + toStr(k));
321
                currentColl.removeAll(v);
322
                if (currentColl.isEmpty())
323
                    removeK = true;
324
            }
325
        }
326
        if (removeK)
327
            if (iter == null)
328
                this.remove(k);
329
            else
330
                iter.remove();
331
    }
332
 
333
    public final void removeAll(Map<? extends K, ? extends Collection<? extends V>> mm) {
334
        // iterate on this to allow mm.removeAll(mm)
335
        final Iterator<Map.Entry<K, C>> iter = this.entrySet().iterator();
336
        while (iter.hasNext()) {
337
            final Map.Entry<K, C> e = iter.next();
338
            final K key = e.getKey();
339
            if (mm.containsKey(key))
340
                this.removeAll(key, mm.get(key), iter);
341
        }
342
    }
343
 
344
    // ** remove empty/null collections
345
 
346
    public final C removeIfEmpty(K k) {
347
        final C v = this.get(k);
348
        if (v != null && v.isEmpty())
349
            return this.remove(k);
350
        else
351
            return null;
352
    }
353
 
354
    public final void removeIfNull(K k) {
355
        if (this.get(k) == null)
356
            this.remove(k);
357
    }
358
 
359
    public final void removeAllEmptyCollections() {
360
        this.removeAll(true);
361
    }
362
 
363
    public final void removeAllNullCollections() {
364
        this.removeAll(false);
365
    }
366
 
367
    private final void removeAll(final boolean emptyOrNull) {
368
        final Iterator<Map.Entry<K, C>> iter = this.entrySet().iterator();
369
        while (iter.hasNext()) {
370
            final Map.Entry<K, C> e = iter.next();
371
            final C val = e.getValue();
372
            if ((emptyOrNull && val != null && val.isEmpty()) || (!emptyOrNull && val == null))
373
                iter.remove();
374
        }
375
    }
376
 
377
    protected abstract C createCollection(Collection<? extends V> v);
378
 
379
    @Override
380
    public int hashCode() {
381
        final int prime = 31;
382
        int result = super.hashCode();
383
        result = prime * result + (this.emptyCollSameAsNoColl ? 1231 : 1237);
384
        result = prime * result + this.mode.hashCode();
385
        return result;
386
    }
387
 
388
    @Override
389
    public boolean equals(Object obj) {
390
        if (this == obj)
391
            return true;
392
        if (!super.equals(obj))
393
            return false;
394
        if (getClass() != obj.getClass())
395
            return false;
396
        // no need to test createCollection(), since values are tested by super.equals()
397
        final CollectionMap2<?, ?, ?> other = (CollectionMap2<?, ?, ?>) obj;
398
        return this.emptyCollSameAsNoColl == other.emptyCollSameAsNoColl && this.mode == other.mode;
399
    }
400
}