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 static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.Beta;
14 import com.google.common.annotations.VisibleForTesting;
15 import com.google.common.collect.ImmutableMap;
16 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
17 import java.util.AbstractMap;
18 import java.util.AbstractSet;
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.ConcurrentModificationException;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.LinkedHashMap;
25 import java.util.List;
27 import java.util.NoSuchElementException;
29 import org.eclipse.jdt.annotation.NonNull;
32 * A mutable version of {@link ImmutableOffsetMap}. It inherits the set of mappings from the immutable version and
33 * allows updating/removing existing mappings. New mappings are stored in a dedicated {@link LinkedHashMap} to preserve
34 * insertion order. It also tracks the need to duplicate the backing array, so the sequence of
36 * ImmutableOffsetMap<K, V> source;
37 * ImmutableOffsetMap<K, V> result = source.createMutableClone().immutableCopy();
39 * results in source and result sharing the backing objects.
41 * <p>This map does not support null keys nor values.
43 * @param <K> the type of keys maintained by this map
44 * @param <V> the type of mapped values
47 public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implements Cloneable, ModifiableMapPhase<K, V> {
48 static final class Ordered<K, V> extends MutableOffsetMap<K, V> {
50 super(new LinkedHashMap<>());
53 Ordered(final Map<K, V> source) {
54 super(OffsetMapCache.orderedOffsets(source.keySet()), source, new LinkedHashMap<>());
57 Ordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
58 super(offsets, objects, new LinkedHashMap<>());
62 Object removedObject() {
67 UnmodifiableMapPhase<K, V> modifiedMap(final List<K> keys, final V[] values) {
68 return new ImmutableOffsetMap.Ordered<>(OffsetMapCache.orderedOffsets(keys), values);
72 UnmodifiableMapPhase<K, V> unmodifiedMap(final ImmutableMap<K, Integer> offsetMap, final V[] values) {
73 return new ImmutableOffsetMap.Ordered<>(offsetMap, values);
77 SharedSingletonMap<K, V> singletonMap() {
78 return SharedSingletonMap.orderedCopyOf(this);
82 static final class Unordered<K, V> extends MutableOffsetMap<K, V> {
84 super(new HashMap<>());
87 Unordered(final Map<K, V> source) {
88 super(OffsetMapCache.unorderedOffsets(source.keySet()), source, new HashMap<>());
91 Unordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
92 super(offsets, objects, new HashMap<>());
96 Object removedObject() {
101 UnmodifiableMapPhase<K, V> modifiedMap(final List<K> keys, final V[] values) {
102 final ImmutableMap<K, Integer> offsets = OffsetMapCache.unorderedOffsets(keys);
103 return new ImmutableOffsetMap.Unordered<>(offsets, OffsetMapCache.adjustedArray(offsets, keys, values));
107 UnmodifiableMapPhase<K, V> unmodifiedMap(final ImmutableMap<K, Integer> offsetMap, final V[] values) {
108 return new ImmutableOffsetMap.Unordered<>(offsetMap, values);
112 SharedSingletonMap<K, V> singletonMap() {
113 return SharedSingletonMap.unorderedCopyOf(this);
117 private static final Object[] EMPTY_ARRAY = new Object[0];
118 private static final Object REMOVED = new Object();
120 private final ImmutableMap<K, Integer> offsets;
121 private HashMap<K, V> newKeys;
122 private Object[] objects;
123 private int removed = 0;
125 // Fail-fast iterator guard, see java.util.ArrayList for reference.
126 @SuppressFBWarnings("VO_VOLATILE_INCREMENT")
127 private transient volatile int modCount;
128 private boolean needClone = true;
130 MutableOffsetMap(final ImmutableMap<K, Integer> offsets, final V[] objects, final HashMap<K, V> newKeys) {
131 verify(newKeys.isEmpty());
132 this.offsets = requireNonNull(offsets);
133 this.objects = requireNonNull(objects);
134 this.newKeys = requireNonNull(newKeys);
137 @SuppressWarnings("unchecked")
138 MutableOffsetMap(final HashMap<K, V> newKeys) {
139 this(ImmutableMap.of(), (V[]) EMPTY_ARRAY, newKeys);
142 @SuppressWarnings("unchecked")
143 MutableOffsetMap(final ImmutableMap<K, Integer> offsets, final Map<K, V> source, final HashMap<K, V> newKeys) {
144 this(offsets, (V[]) new Object[offsets.size()], newKeys);
146 for (Entry<K, V> e : source.entrySet()) {
147 objects[offsets.get(e.getKey())] = requireNonNull(e.getValue());
150 this.needClone = false;
154 * Create a {@link MutableOffsetMap} of the specified map, retaining its iteration order.
156 * @param map input map
157 * @return MutableOffsetMap with the same iteration order
158 * @throws NullPointerException if {@code map} is null
160 public static <K, V> @NonNull MutableOffsetMap<K, V> orderedCopyOf(final Map<K, V> map) {
161 if (map instanceof Ordered) {
162 return ((Ordered<K, V>) map).clone();
164 if (map instanceof ImmutableOffsetMap) {
165 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) map;
166 return new Ordered<>(om.offsets(), om.objects());
169 return new Ordered<>(map);
173 * Create a {@link MutableOffsetMap} of the specified map, potentially with a different iteration order.
175 * @param map input map
176 * @return MutableOffsetMap with undefined iteration order
177 * @throws NullPointerException if {@code map} is null
179 public static <K, V> @NonNull MutableOffsetMap<K, V> unorderedCopyOf(final Map<K, V> map) {
180 if (map instanceof Unordered) {
181 return ((Unordered<K, V>) map).clone();
183 if (map instanceof ImmutableOffsetMap) {
184 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) map;
185 return new Unordered<>(om.offsets(), om.objects());
188 return new Unordered<>(map);
192 * Create an empty {@link MutableOffsetMap} which has an iteration order matching the insertion order.
194 * @return MutableOffsetMap which preserves insertion order
196 public static <K, V> @NonNull MutableOffsetMap<K, V> ordered() {
197 return new MutableOffsetMap.Ordered<>();
201 * Create an empty {@link MutableOffsetMap} which has unspecified iteration order.
203 * @return An MutableOffsetMap
205 public static <K, V> @NonNull MutableOffsetMap<K, V> unordered() {
206 return new MutableOffsetMap.Unordered<>();
209 abstract Object removedObject();
211 abstract UnmodifiableMapPhase<K, V> modifiedMap(List<K> keys, V[] values);
213 abstract UnmodifiableMapPhase<K, V> unmodifiedMap(ImmutableMap<K, Integer> offsetMap, V[] values);
215 abstract SharedSingletonMap<K, V> singletonMap();
218 public final int size() {
219 return offsets.size() + newKeys.size() - removed;
223 public final boolean isEmpty() {
228 public final boolean containsKey(final Object key) {
229 final Integer offset = offsets.get(key);
230 if (offset != null) {
231 final Object obj = objects[offset];
232 if (!REMOVED.equals(obj)) {
237 return newKeys.containsKey(key);
241 public final V get(final Object key) {
242 final Integer offset = offsets.get(key);
243 if (offset != null) {
244 final Object obj = objects[offset];
247 * This is a bit tricky: Ordered will put REMOVED to removed objects to retain strict insertion order.
248 * Unordered will add null, indicating that the slot may be reused in future. Hence if we see a REMOVED
249 * marker, we need to fall back to checking with new keys.
251 if (!REMOVED.equals(obj)) {
252 @SuppressWarnings("unchecked")
253 final V ret = (V)obj;
258 return newKeys.get(key);
261 private void cloneArray() {
264 if (objects.length != 0) {
265 objects = objects.clone();
271 public final V put(final K key, final V value) {
272 requireNonNull(value);
273 final Integer offset = offsets.get(requireNonNull(key));
274 if (offset != null) {
275 final Object obj = objects[offset];
278 * Put which can potentially replace something in objects. Replacing an object does not cause iterators
279 * to be invalidated and does follow insertion order (since it is not a fresh insert). If the object has
280 * been removed, we fall back to newKeys.
282 if (!REMOVED.equals(obj)) {
283 @SuppressWarnings("unchecked")
284 final V ret = (V)obj;
287 objects[offset] = value;
297 final V ret = newKeys.put(key, value);
305 public final V remove(final Object key) {
306 final Integer offset = offsets.get(key);
307 if (offset != null) {
308 final Object obj = objects[offset];
311 * A previous remove() may have indicated that the objects slot cannot be reused. In that case we need
312 * to fall back to checking with newKeys.
314 if (!REMOVED.equals(obj)) {
317 @SuppressWarnings("unchecked")
318 final V ret = (V)obj;
319 objects[offset] = removedObject();
328 final V ret = newKeys.remove(key);
336 public final void clear() {
340 Arrays.fill(objects, removedObject());
341 removed = objects.length;
347 public final Set<Entry<K, V>> entrySet() {
348 return new EntrySet();
352 public Map<K, V> toUnmodifiableMap() {
353 if (removed == 0 && newKeys.isEmpty()) {
354 // Make sure next modification clones the array, as we leak it to the map we return.
357 // We have ended up with no removed objects, hence this cast is safe
358 @SuppressWarnings("unchecked")
359 final V[] values = (V[])objects;
362 * TODO: we could track the ImmutableOffsetMap from which this one was instantiated and if we do not
363 * perform any modifications, just return the original instance. The trade-off is increased complexity
364 * and an additional field in this class.
366 return unmodifiedMap(offsets, values);
369 final int s = size();
371 return ImmutableMap.of();
374 return singletonMap();
377 // Construct the set of keys
378 final List<K> keyset = new ArrayList<>(s);
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 keyset.add(e.getKey());
389 keyset.addAll(offsets.keySet());
391 keyset.addAll(newKeys.keySet());
393 // Construct the values
394 @SuppressWarnings("unchecked")
395 final V[] values = (V[])new Object[keyset.size()];
398 if (removed != offsets.size()) {
399 for (Entry<K, Integer> e : offsets.entrySet()) {
400 final Object o = objects[e.getValue()];
401 if (o != null && !REMOVED.equals(o)) {
402 @SuppressWarnings("unchecked")
404 values[offset++] = v;
409 System.arraycopy(objects, 0, values, 0, offsets.size());
410 offset = offsets.size();
412 for (V v : newKeys.values()) {
413 values[offset++] = v;
416 return modifiedMap(keyset, values);
419 @SuppressWarnings("unchecked")
421 public MutableOffsetMap<K, V> clone() {
422 final MutableOffsetMap<K, V> ret;
425 ret = (MutableOffsetMap<K, V>) super.clone();
426 } catch (CloneNotSupportedException e) {
427 throw new IllegalStateException("Clone is expected to work", e);
430 ret.newKeys = (HashMap<K, V>) newKeys.clone();
431 ret.needClone = true;
436 public final int hashCode() {
439 for (Entry<K, Integer> e : offsets.entrySet()) {
440 final Object v = objects[e.getValue()];
442 result += e.getKey().hashCode() ^ v.hashCode();
446 return result + newKeys.hashCode();
450 public final boolean equals(final Object obj) {
454 if (!(obj instanceof Map)) {
458 if (obj instanceof ImmutableOffsetMap) {
459 final ImmutableOffsetMap<?, ?> om = (ImmutableOffsetMap<?, ?>) obj;
461 if (newKeys.isEmpty() && offsets.equals(om.offsets())) {
462 return Arrays.deepEquals(objects, om.objects());
464 } else if (obj instanceof MutableOffsetMap) {
465 final MutableOffsetMap<?, ?> om = (MutableOffsetMap<?, ?>) obj;
467 if (offsets.equals(om.offsets)) {
468 return Arrays.deepEquals(objects, om.objects) && newKeys.equals(om.newKeys);
472 // Fall back to brute map compare
473 final Map<?, ?> other = (Map<?, ?>)obj;
475 // Size and key sets have to match
476 if (size() != other.size() || !keySet().equals(other.keySet())) {
481 // Ensure all newKeys are present. Note newKeys is guaranteed to
482 // not contain null value.
483 for (Entry<K, V> e : newKeys.entrySet()) {
484 if (!e.getValue().equals(other.get(e.getKey()))) {
489 // Ensure all objects are present
490 for (Entry<K, Integer> e : offsets.entrySet()) {
491 final Object val = objects[e.getValue()];
492 if (val != null && !REMOVED.equals(val) && !val.equals(other.get(e.getKey()))) {
496 } catch (ClassCastException e) {
497 // Can be thrown by other.get() and indicate we have incompatible key types
505 public final Set<K> keySet() {
510 final boolean needClone() {
515 final Object array() {
520 final Object newKeys() {
524 private final class EntrySet extends AbstractSet<Entry<K, V>> {
526 public Iterator<Entry<K, V>> iterator() {
527 return new AbstractSetIterator<Entry<K, V>>() {
529 public Entry<K, V> next() {
530 final K key = nextKey();
531 return new SimpleEntry<>(key, get(key));
538 return MutableOffsetMap.this.size();
542 @SuppressWarnings("checkstyle:parameterName")
543 public boolean contains(final Object o) {
544 if (!(o instanceof Entry)) {
548 @SuppressWarnings("unchecked")
549 final Entry<K,V> e = (Entry<K,V>) o;
550 if (e.getValue() == null) {
554 return e.getValue().equals(MutableOffsetMap.this.get(e.getKey()));
558 @SuppressWarnings("checkstyle:parameterName")
559 public boolean add(final Entry<K, V> e) {
560 final V v = requireNonNull(e.getValue());
561 final V p = MutableOffsetMap.this.put(e.getKey(), v);
566 @SuppressWarnings("checkstyle:parameterName")
567 public boolean remove(final Object o) {
568 if (!(o instanceof Entry)) {
572 @SuppressWarnings("unchecked")
573 final Entry<K,V> e = (Entry<K,V>) o;
574 if (e.getValue() == null) {
578 final V v = MutableOffsetMap.this.get(e.getKey());
579 if (e.getValue().equals(v)) {
580 MutableOffsetMap.this.remove(e.getKey());
587 public void clear() {
588 MutableOffsetMap.this.clear();
592 private final class KeySet extends AbstractSet<K> {
594 public Iterator<K> iterator() {
595 return new AbstractSetIterator<K>() {
605 return MutableOffsetMap.this.size();
609 private abstract class AbstractSetIterator<E> implements Iterator<E> {
610 private final Iterator<Entry<K, Integer>> oldIterator = offsets.entrySet().iterator();
611 private final Iterator<K> newIterator = newKeys.keySet().iterator();
612 private int expectedModCount = modCount;
613 private K currentKey;
616 AbstractSetIterator() {
620 private void updateNextKey() {
621 while (oldIterator.hasNext()) {
622 final Entry<K, Integer> e = oldIterator.next();
623 final Object obj = objects[e.getValue()];
624 if (obj != null && !REMOVED.equals(obj)) {
625 nextKey = e.getKey();
630 nextKey = newIterator.hasNext() ? newIterator.next() : null;
633 private void checkModCount() {
634 if (modCount != expectedModCount) {
635 throw new ConcurrentModificationException();
640 public final boolean hasNext() {
642 return nextKey != null;
646 public final void remove() {
647 requireNonNull(currentKey != null);
650 final Integer offset = offsets.get(currentKey);
651 if (offset != null) {
653 objects[offset] = removedObject();
656 newIterator.remove();
659 expectedModCount = ++modCount;
663 protected final K nextKey() {
664 if (nextKey == null) {
665 throw new NoSuchElementException();
669 currentKey = nextKey;