2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.util;
10 import com.google.common.annotations.Beta;
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Preconditions;
13 import com.google.common.collect.ImmutableMap;
14 import java.util.AbstractMap;
15 import java.util.AbstractSet;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.ConcurrentModificationException;
21 import java.util.Iterator;
22 import java.util.LinkedHashMap;
24 import java.util.NoSuchElementException;
28 * A mutable version of {@link ImmutableOffsetMap}. It inherits the set of mappings from the immutable version and
29 * allows updating/removing existing mappings. New mappings are stored in a dedicated {@link LinkedHashMap} to preserve
30 * insertion order. It also tracks the need to duplicate the backing array, so the sequence of
32 * ImmutableOffsetMap<K, V> source;
33 * ImmutableOffsetMap<K, V> result = source.createMutableClone().immutableCopy();
35 * results in source and result sharing the backing objects.
37 * This map does not support null keys nor values.
39 * @param <K> the type of keys maintained by this map
40 * @param <V> the type of mapped values
43 public final class MutableOffsetMap<K, V> extends AbstractMap<K, V> implements Cloneable, ModifiableMapPhase<K, V> {
44 private static final Object[] EMPTY_ARRAY = new Object[0];
45 private final Map<K, Integer> offsets;
46 private final Map<K, V> newKeys;
48 private int removed = 0;
49 private transient volatile int modCount;
50 private boolean needClone = true;
52 public MutableOffsetMap() {
53 this(Collections.<K>emptySet());
56 @SuppressWarnings("unchecked")
57 protected MutableOffsetMap(final Collection<K> keySet) {
58 if (!keySet.isEmpty()) {
59 removed = keySet.size();
60 offsets = OffsetMapCache.offsetsFor(keySet);
61 objects = (V[])new Object[removed];
63 offsets = ImmutableMap.of();
64 objects = (V[])EMPTY_ARRAY;
67 this.newKeys = new LinkedHashMap<>();
70 protected MutableOffsetMap(final ImmutableOffsetMap<K, V> m) {
71 this(m.offsets(), m.objects());
74 @SuppressWarnings("unchecked")
75 protected MutableOffsetMap(final Map<K, V> m) {
76 this(OffsetMapCache.offsetsFor(m.keySet()), (V[])m.values().toArray());
79 protected MutableOffsetMap(final MutableOffsetMap<K, V> m) {
80 this.offsets = m.offsets;
81 this.objects = m.objects;
82 this.newKeys = new LinkedHashMap<>(m.newKeys);
83 this.removed = m.removed;
86 private MutableOffsetMap(final Map<K, Integer> offsets, final V[] objects) {
87 this.offsets = Preconditions.checkNotNull(offsets);
88 this.objects = Preconditions.checkNotNull(objects);
89 this.newKeys = new LinkedHashMap<>();
92 public static <K, V> MutableOffsetMap<K, V> copyOf(final Map<K, V> m) {
93 if (m instanceof MutableOffsetMap) {
94 return ((MutableOffsetMap<K, V>) m).clone();
96 if (m instanceof ImmutableOffsetMap) {
97 return ((ImmutableOffsetMap<K, V>) m).toModifiableMap();
100 return new MutableOffsetMap<>(m);
103 public static <K, V> MutableOffsetMap<K, V> forOffsets(final Map<K, Integer> offsets) {
104 @SuppressWarnings("unchecked")
105 final V[] objects = (V[]) new Object[offsets.size()];
106 return new MutableOffsetMap<>(offsets, objects);
109 public static <K, V> MutableOffsetMap<K, V> forKeySet(final Collection<K> keySet) {
110 return forOffsets(OffsetMapCache.offsetsFor(keySet));
115 return offsets.size() + newKeys.size() - removed;
119 public boolean isEmpty() {
124 public boolean containsKey(final Object key) {
125 final Integer offset = offsets.get(key);
126 return offset == null ? newKeys.containsKey(key) : objects[offset] != null;
130 public V get(final Object key) {
131 final Integer offset = offsets.get(key);
132 return offset == null ? newKeys.get(key) : objects[offset];
135 private void cloneArray() {
138 if (!EMPTY_ARRAY.equals(objects)) {
139 objects = objects.clone();
145 public V put(final K key, final V value) {
146 Preconditions.checkNotNull(value);
147 final Integer offset = offsets.get(Preconditions.checkNotNull(key));
148 if (offset == null) {
149 final V ret = newKeys.put(key, value);
157 final V ret = objects[offset];
158 objects[offset] = value;
168 public V remove(final Object key) {
169 final Integer offset = offsets.get(key);
170 if (offset == null) {
171 final V ret = newKeys.remove(key);
179 final V ret = objects[offset];
180 objects[offset] = null;
189 public void clear() {
193 Arrays.fill(objects, null);
194 removed = objects.length;
200 public Set<Entry<K, V>> entrySet() {
201 return new EntrySet();
205 public Map<K, V> toUnmodifiableMap() {
206 if (newKeys.isEmpty() && removed == 0) {
207 // Make sure next modification clones the array, as we leak it to the map we return.
210 * TODO: we could track the ImmutableOffsetMap from which this one was instantiated and if we do not
211 * perform any modifications, just return the original instance. The trade-off is increased complexity
212 * and an additional field in this class.
214 return new ImmutableOffsetMap<>(offsets, objects);
217 final int s = size();
219 return ImmutableMap.of();
222 return ImmutableMap.copyOf(this);
225 // Construct the set of keys
226 final Collection<K> keyset = new ArrayList<>(s);
228 if (removed != offsets.size()) {
229 for (Entry<K, Integer> e : offsets.entrySet()) {
230 if (objects[e.getValue()] != null) {
231 keyset.add(e.getKey());
236 keyset.addAll(offsets.keySet());
238 keyset.addAll(newKeys.keySet());
240 // Construct the values
241 @SuppressWarnings("unchecked")
242 final V[] values = (V[])new Object[keyset.size()];
245 if (removed != offsets.size()) {
246 for (Entry<K, Integer> e : offsets.entrySet()) {
247 final V o = objects[e.getValue()];
254 System.arraycopy(objects, 0, values, 0, offsets.size());
257 for (V v : newKeys.values()) {
261 return new ImmutableOffsetMap<>(OffsetMapCache.offsetsFor(keyset), values);
265 public MutableOffsetMap<K, V> clone() {
266 // FIXME: super.clone()
267 return new MutableOffsetMap<K, V>(this);
271 public int hashCode() {
274 for (Entry<K, Integer> e : offsets.entrySet()) {
275 final Object v = objects[e.getValue()];
277 result += e.getKey().hashCode() ^ v.hashCode();
281 return result + newKeys.hashCode();
285 public boolean equals(final Object o) {
289 if (!(o instanceof Map)) {
293 if (o instanceof ImmutableOffsetMap) {
294 final ImmutableOffsetMap<?, ?> om = (ImmutableOffsetMap<?, ?>) o;
296 if (newKeys.isEmpty() && offsets.equals(om.offsets())) {
297 return Arrays.deepEquals(objects, om.objects());
299 } else if (o instanceof MutableOffsetMap) {
300 final MutableOffsetMap<?, ?> om = (MutableOffsetMap<?, ?>) o;
302 if (offsets.equals(om.offsets)) {
303 return Arrays.deepEquals(objects, om.objects) && newKeys.equals(om.newKeys);
307 // Fall back to brute map compare
308 final Map<?, ?> other = (Map<?, ?>)o;
310 // Size and key sets have to match
311 if (size() != other.size() || !keySet().equals(other.keySet())) {
316 // Ensure all newKeys are present. Note newKeys is guaranteed to
317 // not contain null value.
318 for (Entry<K, V> e : newKeys.entrySet()) {
319 if (!e.getValue().equals(other.get(e.getKey()))) {
324 // Ensure all objects are present
325 for (Entry<K, Integer> e : offsets.entrySet()) {
326 final V v = objects[e.getValue()];
327 if (v != null && !v.equals(other.get(e.getKey()))) {
331 } catch (ClassCastException e) {
332 // Can be thrown by other.get() and indicate we have incompatible key types
340 public Set<K> keySet() {
345 boolean needClone() {
359 private final class EntrySet extends AbstractSet<Entry<K, V>> {
361 public Iterator<Entry<K, V>> iterator() {
362 return new AbstractSetIterator<Entry<K, V>>() {
364 public Entry<K, V> next() {
365 final K key = nextKey();
366 return new SimpleEntry<>(key, get(key));
373 return MutableOffsetMap.this.size();
377 public boolean contains(final Object o) {
378 if (!(o instanceof Entry)) {
382 @SuppressWarnings("unchecked")
383 final Entry<K,V> e = (Entry<K,V>) o;
384 if (e.getValue() == null) {
388 return e.getValue().equals(MutableOffsetMap.this.get(e.getKey()));
392 public boolean add(final Entry<K, V> e) {
393 Preconditions.checkNotNull(e.getValue());
394 final V p = MutableOffsetMap.this.put(e.getKey(), e.getValue());
395 return !e.getValue().equals(p);
399 public boolean remove(final Object o) {
400 if (!(o instanceof Entry)) {
404 @SuppressWarnings("unchecked")
405 final Entry<K,V> e = (Entry<K,V>) o;
406 if (e.getValue() == null) {
410 final V v = MutableOffsetMap.this.get(e.getKey());
411 if (e.getValue().equals(v)) {
412 MutableOffsetMap.this.remove(e.getKey());
419 public void clear() {
420 MutableOffsetMap.this.clear();
424 private final class KeySet extends AbstractSet<K> {
426 public Iterator<K> iterator() {
427 return new AbstractSetIterator<K>() {
437 return MutableOffsetMap.this.size();
441 private abstract class AbstractSetIterator<E> implements Iterator<E> {
442 private final Iterator<Entry<K, Integer>> oldIterator = offsets.entrySet().iterator();
443 private final Iterator<K> newIterator = newKeys.keySet().iterator();
444 private int expectedModCount = modCount;
445 private K currentKey, nextKey;
447 AbstractSetIterator() {
451 private void updateNextKey() {
452 while (oldIterator.hasNext()) {
453 final Entry<K, Integer> e = oldIterator.next();
454 if (objects[e.getValue()] != null) {
455 nextKey = e.getKey();
460 nextKey = newIterator.hasNext() ? newIterator.next() : null;
463 private void checkModCount() {
464 if (modCount != expectedModCount) {
465 throw new ConcurrentModificationException();
470 public final boolean hasNext() {
472 return nextKey != null;
476 public final void remove() {
477 Preconditions.checkState(currentKey != null);
480 final Integer offset = offsets.get(currentKey);
481 if (offset != null) {
483 objects[offset] = null;
486 newIterator.remove();
489 expectedModCount = ++modCount;
493 protected final K nextKey() {
494 if (nextKey == null) {
495 throw new NoSuchElementException();
499 currentKey = nextKey;