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.base.Verify;
14 import com.google.common.collect.ImmutableMap;
15 import java.util.AbstractMap;
16 import java.util.AbstractSet;
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.ConcurrentModificationException;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.LinkedHashMap;
23 import java.util.List;
25 import java.util.NoSuchElementException;
29 * A mutable version of {@link ImmutableOffsetMap}. It inherits the set of mappings from the immutable version and
30 * allows updating/removing existing mappings. New mappings are stored in a dedicated {@link LinkedHashMap} to preserve
31 * insertion order. It also tracks the need to duplicate the backing array, so the sequence of
33 * ImmutableOffsetMap<K, V> source;
34 * ImmutableOffsetMap<K, V> result = source.createMutableClone().immutableCopy();
36 * results in source and result sharing the backing objects.
38 * This map does not support null keys nor values.
40 * @param <K> the type of keys maintained by this map
41 * @param <V> the type of mapped values
44 public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implements Cloneable, ModifiableMapPhase<K, V> {
45 static final class Ordered<K, V> extends MutableOffsetMap<K, V> {
47 super(new LinkedHashMap<K, V>());
50 Ordered(final Map<K, V> source) {
51 super(OffsetMapCache.orderedOffsets(source.keySet()), source, new LinkedHashMap<K, V>());
54 Ordered(final Map<K, Integer> offsets, final V[] objects) {
55 super(offsets, objects, new LinkedHashMap<K, V>());
59 Object removedObject() {
64 UnmodifiableMapPhase<K, V> modifiedMap(final List<K> keys, final V[] objects) {
65 return new ImmutableOffsetMap.Ordered<>(OffsetMapCache.orderedOffsets(keys), objects);
69 UnmodifiableMapPhase<K, V> unmodifiedMap(final Map<K, Integer> offsets, final V[] objects) {
70 return new ImmutableOffsetMap.Ordered<>(offsets, objects);
74 SharedSingletonMap<K, V> singletonMap() {
75 return SharedSingletonMap.orderedCopyOf(this);
79 static final class Unordered<K, V> extends MutableOffsetMap<K, V> {
81 super(new HashMap<K, V>());
84 Unordered(final Map<K, V> source) {
85 super(OffsetMapCache.unorderedOffsets(source.keySet()), source, new HashMap<K, V>());
88 Unordered(final Map<K, Integer> offsets, final V[] objects) {
89 super(offsets, objects, new HashMap<K, V>());
93 Object removedObject() {
98 UnmodifiableMapPhase<K, V> modifiedMap(final List<K> keys, final V[] objects) {
99 final Map<K, Integer> offsets = OffsetMapCache.unorderedOffsets(keys);
100 return new ImmutableOffsetMap.Unordered<>(offsets, OffsetMapCache.adjustedArray(offsets, keys, objects));
104 UnmodifiableMapPhase<K, V> unmodifiedMap(final Map<K, Integer> offsets, final V[] objects) {
105 return new ImmutableOffsetMap.Unordered<>(offsets, objects);
109 SharedSingletonMap<K, V> singletonMap() {
110 return SharedSingletonMap.unorderedCopyOf(this);
114 private static final Object[] EMPTY_ARRAY = new Object[0];
115 private static final Object REMOVED = new Object();
116 private final Map<K, Integer> offsets;
117 private HashMap<K, V> newKeys;
118 private Object[] objects;
119 private int removed = 0;
120 private transient volatile int modCount;
121 private boolean needClone = true;
123 MutableOffsetMap(final Map<K, Integer> offsets, final V[] objects, final HashMap<K, V> newKeys) {
124 Verify.verify(newKeys.isEmpty());
125 this.offsets = Preconditions.checkNotNull(offsets);
126 this.objects = Preconditions.checkNotNull(objects);
127 this.newKeys = Preconditions.checkNotNull(newKeys);
130 @SuppressWarnings("unchecked")
131 MutableOffsetMap(final HashMap<K, V> newKeys) {
132 this(ImmutableMap.<K, Integer>of(), (V[]) EMPTY_ARRAY, newKeys);
135 @SuppressWarnings("unchecked")
136 MutableOffsetMap(final Map<K, Integer> offsets, final Map<K, V> source, final HashMap<K, V> newKeys) {
137 this(offsets, (V[]) new Object[offsets.size()], newKeys);
139 for (Entry<K, V> e : source.entrySet()) {
140 objects[offsets.get(e.getKey())] = Preconditions.checkNotNull(e.getValue());
143 this.needClone = false;
147 * @deprecated Use {@link #orderedCopyOf(Map)} or {@link #unorderedCopyOf(Map)} instead.
150 public static <K, V> MutableOffsetMap<K, V> copyOf(final Map<K, V> m) {
151 return orderedCopyOf(m);
154 public static <K, V> MutableOffsetMap<K, V> orderedCopyOf(final Map<K, V> m) {
155 if (m instanceof Ordered) {
156 return ((Ordered<K, V>) m).clone();
158 if (m instanceof ImmutableOffsetMap) {
159 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) m;
160 return new Ordered<>(om.offsets(), om.objects());
163 return new Ordered<>(m);
166 public static <K, V> MutableOffsetMap<K, V> unorderedCopyOf(final Map<K, V> m) {
167 if (m instanceof Unordered) {
168 return ((Unordered<K, V>) m).clone();
170 if (m instanceof ImmutableOffsetMap) {
171 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) m;
172 return new Unordered<>(om.offsets(), om.objects());
175 return new Unordered<>(m);
179 * @deprecated Use {@link #ordered()} or {@link #unordered()} instead.
182 public static <K, V> MutableOffsetMap<K, V> of() {
186 public static <K, V> MutableOffsetMap<K, V> ordered() {
187 return new MutableOffsetMap.Ordered<>();
190 public static <K, V> MutableOffsetMap<K, V> unordered() {
191 return new MutableOffsetMap.Unordered<>();
194 abstract Object removedObject();
195 abstract UnmodifiableMapPhase<K, V> modifiedMap(List<K> keys, V[] objects);
196 abstract UnmodifiableMapPhase<K, V> unmodifiedMap(Map<K, Integer> offsets, V[] objects);
197 abstract SharedSingletonMap<K, V> singletonMap();
200 public final int size() {
201 return offsets.size() + newKeys.size() - removed;
205 public final boolean isEmpty() {
210 public final boolean containsKey(final Object key) {
211 final Integer offset = offsets.get(key);
212 if (offset != null) {
213 final Object obj = objects[offset];
214 if (!REMOVED.equals(obj)) {
219 return newKeys.containsKey(key);
223 public final V get(final Object key) {
224 final Integer offset = offsets.get(key);
225 if (offset != null) {
226 final Object obj = objects[offset];
229 * This is a bit tricky: Ordered will put REMOVED to removed objects to retain strict insertion order.
230 * Unordered will add null, indicating that the slot may be reused in future. Hence if we see a REMOVED
231 * marker, we need to fall back to checking with new keys.
233 if (!REMOVED.equals(obj)) {
234 @SuppressWarnings("unchecked")
235 final V ret = (V)obj;
240 return newKeys.get(key);
243 private void cloneArray() {
246 if (!EMPTY_ARRAY.equals(objects)) {
247 objects = objects.clone();
253 public final V put(final K key, final V value) {
254 Preconditions.checkNotNull(value);
255 final Integer offset = offsets.get(Preconditions.checkNotNull(key));
256 if (offset != null) {
257 final Object obj = objects[offset];
260 * Put which can potentially replace something in objects. Replacing an object does not cause iterators
261 * to be invalidated and does follow insertion order (since it is not a fresh insert). If the object has
262 * been removed, we fall back to newKeys.
264 if (!REMOVED.equals(obj)) {
265 @SuppressWarnings("unchecked")
266 final V ret = (V)obj;
269 objects[offset] = value;
279 final V ret = newKeys.put(key, value);
287 public final V remove(final Object key) {
288 final Integer offset = offsets.get(key);
289 if (offset != null) {
290 final Object obj = objects[offset];
293 * A previous remove() may have indicated that the objects slot cannot be reused. In that case we need
294 * to fall back to checking with newKeys.
296 if (!REMOVED.equals(obj)) {
299 @SuppressWarnings("unchecked")
300 final V ret = (V)obj;
301 objects[offset] = removedObject();
310 final V ret = newKeys.remove(key);
318 public final void clear() {
322 Arrays.fill(objects, removedObject());
323 removed = objects.length;
329 public final Set<Entry<K, V>> entrySet() {
330 return new EntrySet();
334 public Map<K, V> toUnmodifiableMap() {
335 if (removed == 0 && newKeys.isEmpty()) {
336 // Make sure next modification clones the array, as we leak it to the map we return.
339 // We have ended up with no removed objects, hence this cast is safe
340 @SuppressWarnings("unchecked")
341 final V[] values = (V[])objects;
344 * TODO: we could track the ImmutableOffsetMap from which this one was instantiated and if we do not
345 * perform any modifications, just return the original instance. The trade-off is increased complexity
346 * and an additional field in this class.
348 return unmodifiedMap(offsets, values);
351 final int s = size();
353 return ImmutableMap.of();
356 return singletonMap();
359 // Construct the set of keys
360 final List<K> keyset = new ArrayList<>(s);
362 if (removed != offsets.size()) {
363 for (Entry<K, Integer> e : offsets.entrySet()) {
364 final Object o = objects[e.getValue()];
365 if (o != null && !REMOVED.equals(o)) {
366 keyset.add(e.getKey());
371 keyset.addAll(offsets.keySet());
373 keyset.addAll(newKeys.keySet());
375 // Construct the values
376 @SuppressWarnings("unchecked")
377 final V[] values = (V[])new Object[keyset.size()];
380 if (removed != offsets.size()) {
381 for (Entry<K, Integer> e : offsets.entrySet()) {
382 final Object o = objects[e.getValue()];
383 if (o != null && !REMOVED.equals(o)) {
384 @SuppressWarnings("unchecked")
391 System.arraycopy(objects, 0, values, 0, offsets.size());
394 for (V v : newKeys.values()) {
398 return modifiedMap(keyset, values);
401 @SuppressWarnings("unchecked")
403 public MutableOffsetMap<K, V> clone() {
404 final MutableOffsetMap<K, V> ret;
407 ret = (MutableOffsetMap<K, V>) super.clone();
408 } catch (CloneNotSupportedException e) {
409 throw new IllegalStateException("Clone is expected to work", e);
412 ret.newKeys = (HashMap<K, V>) newKeys.clone();
413 ret.needClone = true;
418 public final int hashCode() {
421 for (Entry<K, Integer> e : offsets.entrySet()) {
422 final Object v = objects[e.getValue()];
424 result += e.getKey().hashCode() ^ v.hashCode();
428 return result + newKeys.hashCode();
432 public final boolean equals(final Object o) {
436 if (!(o instanceof Map)) {
440 if (o instanceof ImmutableOffsetMap) {
441 final ImmutableOffsetMap<?, ?> om = (ImmutableOffsetMap<?, ?>) o;
443 if (newKeys.isEmpty() && offsets.equals(om.offsets())) {
444 return Arrays.deepEquals(objects, om.objects());
446 } else if (o instanceof MutableOffsetMap) {
447 final MutableOffsetMap<?, ?> om = (MutableOffsetMap<?, ?>) o;
449 if (offsets.equals(om.offsets)) {
450 return Arrays.deepEquals(objects, om.objects) && newKeys.equals(om.newKeys);
454 // Fall back to brute map compare
455 final Map<?, ?> other = (Map<?, ?>)o;
457 // Size and key sets have to match
458 if (size() != other.size() || !keySet().equals(other.keySet())) {
463 // Ensure all newKeys are present. Note newKeys is guaranteed to
464 // not contain null value.
465 for (Entry<K, V> e : newKeys.entrySet()) {
466 if (!e.getValue().equals(other.get(e.getKey()))) {
471 // Ensure all objects are present
472 for (Entry<K, Integer> e : offsets.entrySet()) {
473 final Object obj = objects[e.getValue()];
474 if (obj != null && !REMOVED.equals(obj) && !obj.equals(other.get(e.getKey()))) {
478 } catch (ClassCastException e) {
479 // Can be thrown by other.get() and indicate we have incompatible key types
487 public final Set<K> keySet() {
492 final boolean needClone() {
497 final Object array() {
502 final Object newKeys() {
506 private final class EntrySet extends AbstractSet<Entry<K, V>> {
508 public Iterator<Entry<K, V>> iterator() {
509 return new AbstractSetIterator<Entry<K, V>>() {
511 public Entry<K, V> next() {
512 final K key = nextKey();
513 return new SimpleEntry<>(key, get(key));
520 return MutableOffsetMap.this.size();
524 public boolean contains(final Object o) {
525 if (!(o instanceof Entry)) {
529 @SuppressWarnings("unchecked")
530 final Entry<K,V> e = (Entry<K,V>) o;
531 if (e.getValue() == null) {
535 return e.getValue().equals(MutableOffsetMap.this.get(e.getKey()));
539 public boolean add(final Entry<K, V> e) {
540 Preconditions.checkNotNull(e.getValue());
541 final V p = MutableOffsetMap.this.put(e.getKey(), e.getValue());
542 return !e.getValue().equals(p);
546 public boolean remove(final Object o) {
547 if (!(o instanceof Entry)) {
551 @SuppressWarnings("unchecked")
552 final Entry<K,V> e = (Entry<K,V>) o;
553 if (e.getValue() == null) {
557 final V v = MutableOffsetMap.this.get(e.getKey());
558 if (e.getValue().equals(v)) {
559 MutableOffsetMap.this.remove(e.getKey());
566 public void clear() {
567 MutableOffsetMap.this.clear();
571 private final class KeySet extends AbstractSet<K> {
573 public Iterator<K> iterator() {
574 return new AbstractSetIterator<K>() {
584 return MutableOffsetMap.this.size();
588 private abstract class AbstractSetIterator<E> implements Iterator<E> {
589 private final Iterator<Entry<K, Integer>> oldIterator = offsets.entrySet().iterator();
590 private final Iterator<K> newIterator = newKeys.keySet().iterator();
591 private int expectedModCount = modCount;
592 private K currentKey, nextKey;
594 AbstractSetIterator() {
598 private void updateNextKey() {
599 while (oldIterator.hasNext()) {
600 final Entry<K, Integer> e = oldIterator.next();
601 final Object obj = objects[e.getValue()];
602 if (obj != null && !REMOVED.equals(obj)) {
603 nextKey = e.getKey();
608 nextKey = newIterator.hasNext() ? newIterator.next() : null;
611 private void checkModCount() {
612 if (modCount != expectedModCount) {
613 throw new ConcurrentModificationException();
618 public final boolean hasNext() {
620 return nextKey != null;
624 public final void remove() {
625 Preconditions.checkState(currentKey != null);
628 final Integer offset = offsets.get(currentKey);
629 if (offset != null) {
631 objects[offset] = removedObject();
634 newIterator.remove();
637 expectedModCount = ++modCount;
641 protected final K nextKey() {
642 if (nextKey == null) {
643 throw new NoSuchElementException();
647 currentKey = nextKey;