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.Preconditions.checkState;
11 import static com.google.common.base.Verify.verify;
12 import static java.util.Objects.requireNonNull;
14 import com.google.common.annotations.Beta;
15 import com.google.common.annotations.VisibleForTesting;
16 import com.google.common.collect.ImmutableMap;
17 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
18 import java.util.AbstractMap;
19 import java.util.AbstractSet;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.ConcurrentModificationException;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.LinkedHashMap;
26 import java.util.List;
28 import java.util.NoSuchElementException;
30 import org.eclipse.jdt.annotation.NonNull;
31 import org.eclipse.jdt.annotation.Nullable;
34 * A mutable version of {@link ImmutableOffsetMap}. It inherits the set of mappings from the immutable version and
35 * allows updating/removing existing mappings. New mappings are stored in a dedicated {@link LinkedHashMap} to preserve
36 * insertion order. It also tracks the need to duplicate the backing array, so the sequence of
38 * ImmutableOffsetMap<K, V> source;
39 * ImmutableOffsetMap<K, V> result = source.createMutableClone().immutableCopy();
41 * results in source and result sharing the backing objects.
43 * <p>This map does not support null keys nor values.
45 * @param <K> the type of keys maintained by this map
46 * @param <V> the type of mapped values
49 public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implements Cloneable, ModifiableMapPhase<K, V> {
50 static final class Ordered<K, V> extends MutableOffsetMap<K, V> {
52 super(new LinkedHashMap<>());
55 Ordered(final Map<K, V> source) {
56 super(OffsetMapCache.orderedOffsets(source.keySet()), source, new LinkedHashMap<>());
59 Ordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
60 super(offsets, objects, new LinkedHashMap<>());
64 Object removedObject() {
69 UnmodifiableMapPhase<K, V> modifiedMap(final List<K> keys, final V[] values) {
70 return new ImmutableOffsetMap.Ordered<>(OffsetMapCache.orderedOffsets(keys), values);
74 UnmodifiableMapPhase<K, V> unmodifiedMap(final ImmutableMap<K, Integer> offsetMap, final V[] values) {
75 return new ImmutableOffsetMap.Ordered<>(offsetMap, values);
79 SharedSingletonMap<K, V> singletonMap() {
80 return SharedSingletonMap.orderedCopyOf(this);
84 static final class Unordered<K, V> extends MutableOffsetMap<K, V> {
86 super(new HashMap<>());
89 Unordered(final Map<K, V> source) {
90 super(OffsetMapCache.unorderedOffsets(source.keySet()), source, new HashMap<>());
93 Unordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
94 super(offsets, objects, new HashMap<>());
98 Object removedObject() {
103 UnmodifiableMapPhase<K, V> modifiedMap(final List<K> keys, final V[] values) {
104 final ImmutableMap<K, Integer> offsets = OffsetMapCache.unorderedOffsets(keys);
105 return new ImmutableOffsetMap.Unordered<>(offsets, OffsetMapCache.adjustedArray(offsets, keys, values));
109 UnmodifiableMapPhase<K, V> unmodifiedMap(final ImmutableMap<K, Integer> offsetMap, final V[] values) {
110 return new ImmutableOffsetMap.Unordered<>(offsetMap, values);
114 SharedSingletonMap<K, V> singletonMap() {
115 return SharedSingletonMap.unorderedCopyOf(this);
119 private static final Object[] EMPTY_ARRAY = new Object[0];
120 private static final Object REMOVED = new Object();
122 private final ImmutableMap<K, Integer> offsets;
123 private HashMap<K, V> newKeys;
124 private Object[] objects;
125 private int removed = 0;
127 // Fail-fast iterator guard, see java.util.ArrayList for reference.
128 @SuppressFBWarnings("VO_VOLATILE_INCREMENT")
129 private transient volatile int modCount;
130 private boolean needClone = true;
132 MutableOffsetMap(final ImmutableMap<K, Integer> offsets, final V[] objects, final HashMap<K, V> newKeys) {
133 verify(newKeys.isEmpty());
134 this.offsets = requireNonNull(offsets);
135 this.objects = requireNonNull(objects);
136 this.newKeys = requireNonNull(newKeys);
139 @SuppressWarnings("unchecked")
140 MutableOffsetMap(final HashMap<K, V> newKeys) {
141 this(ImmutableMap.of(), (V[]) EMPTY_ARRAY, newKeys);
144 @SuppressWarnings("unchecked")
145 MutableOffsetMap(final ImmutableMap<K, Integer> offsets, final Map<K, V> source, final HashMap<K, V> newKeys) {
146 this(offsets, (V[]) new Object[offsets.size()], newKeys);
148 for (Entry<K, V> e : source.entrySet()) {
149 objects[offsets.get(e.getKey())] = requireNonNull(e.getValue());
152 this.needClone = false;
156 * Create a {@link MutableOffsetMap} of the specified map, retaining its iteration order.
158 * @param map input map
159 * @return MutableOffsetMap with the same iteration order
160 * @throws NullPointerException if {@code map} is null
162 public static <K, V> @NonNull MutableOffsetMap<K, V> orderedCopyOf(final Map<K, V> map) {
163 if (map instanceof Ordered) {
164 return ((Ordered<K, V>) map).clone();
166 if (map instanceof ImmutableOffsetMap) {
167 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) map;
168 return new Ordered<>(om.offsets(), om.objects());
171 return new Ordered<>(map);
175 * Create a {@link MutableOffsetMap} of the specified map, potentially with a different iteration order.
177 * @param map input map
178 * @return MutableOffsetMap with undefined iteration order
179 * @throws NullPointerException if {@code map} is null
181 public static <K, V> @NonNull MutableOffsetMap<K, V> unorderedCopyOf(final Map<K, V> map) {
182 if (map instanceof Unordered) {
183 return ((Unordered<K, V>) map).clone();
185 if (map instanceof ImmutableOffsetMap) {
186 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) map;
187 return new Unordered<>(om.offsets(), om.objects());
190 return new Unordered<>(map);
194 * Create an empty {@link MutableOffsetMap} which has an iteration order matching the insertion order.
196 * @return MutableOffsetMap which preserves insertion order
198 public static <K, V> @NonNull MutableOffsetMap<K, V> ordered() {
199 return new MutableOffsetMap.Ordered<>();
203 * Create an empty {@link MutableOffsetMap} which has unspecified iteration order.
205 * @return An MutableOffsetMap
207 public static <K, V> @NonNull MutableOffsetMap<K, V> unordered() {
208 return new MutableOffsetMap.Unordered<>();
211 abstract Object removedObject();
213 abstract UnmodifiableMapPhase<K, V> modifiedMap(List<K> keys, V[] values);
215 abstract UnmodifiableMapPhase<K, V> unmodifiedMap(ImmutableMap<K, Integer> offsetMap, V[] values);
217 abstract SharedSingletonMap<K, V> singletonMap();
220 public final int size() {
221 return offsets.size() + newKeys.size() - removed;
225 public final boolean isEmpty() {
230 public final boolean containsKey(final Object key) {
231 final Integer offset = offsets.get(key);
232 if (offset != null) {
233 final Object obj = objects[offset];
234 if (!REMOVED.equals(obj)) {
239 return newKeys.containsKey(key);
243 public final V get(final Object key) {
244 final Integer offset = offsets.get(key);
245 if (offset != null) {
246 final Object obj = objects[offset];
249 * This is a bit tricky: Ordered will put REMOVED to removed objects to retain strict insertion order.
250 * Unordered will add null, indicating that the slot may be reused in future. Hence if we see a REMOVED
251 * marker, we need to fall back to checking with new keys.
253 if (!REMOVED.equals(obj)) {
254 @SuppressWarnings("unchecked")
255 final V ret = (V)obj;
260 return newKeys.get(key);
263 private void cloneArray() {
266 if (objects.length != 0) {
267 objects = objects.clone();
273 public final V put(final K key, final V value) {
274 requireNonNull(value);
275 final Integer offset = offsets.get(requireNonNull(key));
276 if (offset != null) {
277 final Object obj = objects[offset];
280 * Put which can potentially replace something in objects. Replacing an object does not cause iterators
281 * to be invalidated and does follow insertion order (since it is not a fresh insert). If the object has
282 * been removed, we fall back to newKeys.
284 if (!REMOVED.equals(obj)) {
285 @SuppressWarnings("unchecked")
286 final V ret = (V)obj;
289 objects[offset] = value;
299 final V ret = newKeys.put(key, value);
307 public final V remove(final Object key) {
308 final Integer offset = offsets.get(key);
309 if (offset != null) {
310 final Object obj = objects[offset];
313 * A previous remove() may have indicated that the objects slot cannot be reused. In that case we need
314 * to fall back to checking with newKeys.
316 if (!REMOVED.equals(obj)) {
319 @SuppressWarnings("unchecked")
320 final V ret = (V)obj;
321 objects[offset] = removedObject();
330 final V ret = newKeys.remove(key);
338 public final void clear() {
342 Arrays.fill(objects, removedObject());
343 removed = objects.length;
349 public final @NonNull Set<Entry<K, V>> entrySet() {
350 return new EntrySet();
354 public @NonNull Map<K, V> toUnmodifiableMap() {
355 if (removed == 0 && newKeys.isEmpty()) {
356 // Make sure next modification clones the array, as we leak it to the map we return.
359 // We have ended up with no removed objects, hence this cast is safe
360 @SuppressWarnings("unchecked")
361 final V[] values = (V[])objects;
364 * TODO: we could track the ImmutableOffsetMap from which this one was instantiated and if we do not
365 * perform any modifications, just return the original instance. The trade-off is increased complexity
366 * and an additional field in this class.
368 return unmodifiedMap(offsets, values);
371 final int s = size();
373 return ImmutableMap.of();
376 return singletonMap();
379 // Construct the set of keys
380 final List<K> keyset = new ArrayList<>(s);
382 if (removed != offsets.size()) {
383 for (Entry<K, Integer> e : offsets.entrySet()) {
384 final Object o = objects[e.getValue()];
385 if (o != null && !REMOVED.equals(o)) {
386 keyset.add(e.getKey());
391 keyset.addAll(offsets.keySet());
393 keyset.addAll(newKeys.keySet());
395 // Construct the values
396 @SuppressWarnings("unchecked")
397 final V[] values = (V[])new Object[keyset.size()];
400 if (removed != offsets.size()) {
401 for (Entry<K, Integer> e : offsets.entrySet()) {
402 final Object o = objects[e.getValue()];
403 if (o != null && !REMOVED.equals(o)) {
404 @SuppressWarnings("unchecked")
406 values[offset++] = v;
411 System.arraycopy(objects, 0, values, 0, offsets.size());
412 offset = offsets.size();
414 for (V v : newKeys.values()) {
415 values[offset++] = v;
418 return modifiedMap(keyset, values);
421 @SuppressWarnings("unchecked")
423 public MutableOffsetMap<K, V> clone() {
424 final MutableOffsetMap<K, V> ret;
427 ret = (MutableOffsetMap<K, V>) super.clone();
428 } catch (CloneNotSupportedException e) {
429 throw new IllegalStateException("Clone is expected to work", e);
432 ret.newKeys = (HashMap<K, V>) newKeys.clone();
433 ret.needClone = true;
438 public final int hashCode() {
441 for (Entry<K, Integer> e : offsets.entrySet()) {
442 final Object v = objects[e.getValue()];
444 result += e.getKey().hashCode() ^ v.hashCode();
448 return result + newKeys.hashCode();
452 public final boolean equals(final Object obj) {
456 if (!(obj instanceof Map)) {
460 if (obj instanceof ImmutableOffsetMap) {
461 final ImmutableOffsetMap<?, ?> om = (ImmutableOffsetMap<?, ?>) obj;
463 if (newKeys.isEmpty() && offsets.equals(om.offsets())) {
464 return Arrays.deepEquals(objects, om.objects());
466 } else if (obj instanceof MutableOffsetMap) {
467 final MutableOffsetMap<?, ?> om = (MutableOffsetMap<?, ?>) obj;
469 if (offsets.equals(om.offsets)) {
470 return Arrays.deepEquals(objects, om.objects) && newKeys.equals(om.newKeys);
474 // Fall back to brute map compare
475 final Map<?, ?> other = (Map<?, ?>)obj;
477 // Size and key sets have to match
478 if (size() != other.size() || !keySet().equals(other.keySet())) {
483 // Ensure all newKeys are present. Note newKeys is guaranteed to
484 // not contain null value.
485 for (Entry<K, V> e : newKeys.entrySet()) {
486 if (!e.getValue().equals(other.get(e.getKey()))) {
491 // Ensure all objects are present
492 for (Entry<K, Integer> e : offsets.entrySet()) {
493 final Object val = objects[e.getValue()];
494 if (val != null && !REMOVED.equals(val) && !val.equals(other.get(e.getKey()))) {
498 } catch (ClassCastException e) {
499 // Can be thrown by other.get() and indicate we have incompatible key types
507 public final @NonNull Set<K> keySet() {
512 final boolean needClone() {
517 final Object array() {
522 final Object newKeys() {
526 private final class EntrySet extends AbstractSet<Entry<K, V>> {
528 public @NonNull Iterator<Entry<K, V>> iterator() {
529 return new AbstractSetIterator<Entry<K, V>>() {
531 public Entry<K, V> next() {
532 final K key = nextKey();
533 return new SimpleEntry<>(key, get(key));
540 return MutableOffsetMap.this.size();
544 @SuppressWarnings("checkstyle:parameterName")
545 public boolean contains(final Object o) {
546 if (!(o instanceof Entry)) {
550 @SuppressWarnings("unchecked")
551 final Entry<K,V> e = (Entry<K,V>) o;
552 if (e.getValue() == null) {
556 return e.getValue().equals(MutableOffsetMap.this.get(e.getKey()));
560 @SuppressWarnings("checkstyle:parameterName")
561 public boolean add(final Entry<K, V> e) {
562 final V v = requireNonNull(e.getValue());
563 final V p = MutableOffsetMap.this.put(e.getKey(), v);
568 @SuppressWarnings("checkstyle:parameterName")
569 public boolean remove(final Object o) {
570 if (!(o instanceof Entry)) {
574 @SuppressWarnings("unchecked")
575 final Entry<K,V> e = (Entry<K,V>) o;
576 if (e.getValue() == null) {
580 final V v = MutableOffsetMap.this.get(e.getKey());
581 if (e.getValue().equals(v)) {
582 MutableOffsetMap.this.remove(e.getKey());
589 public void clear() {
590 MutableOffsetMap.this.clear();
594 private final class KeySet extends AbstractSet<K> {
596 public @NonNull Iterator<K> iterator() {
597 return new AbstractSetIterator<K>() {
607 return MutableOffsetMap.this.size();
611 private abstract class AbstractSetIterator<E> implements Iterator<E> {
612 private final Iterator<Entry<K, Integer>> oldIterator = offsets.entrySet().iterator();
613 private final Iterator<K> newIterator = newKeys.keySet().iterator();
614 private int expectedModCount = modCount;
615 private @Nullable K currentKey = null;
616 private @Nullable K nextKey;
618 AbstractSetIterator() {
622 private void updateNextKey() {
623 while (oldIterator.hasNext()) {
624 final Entry<K, Integer> e = oldIterator.next();
625 final Object obj = objects[e.getValue()];
626 if (obj != null && !REMOVED.equals(obj)) {
627 nextKey = e.getKey();
632 nextKey = newIterator.hasNext() ? newIterator.next() : null;
635 private void checkModCount() {
636 if (modCount != expectedModCount) {
637 throw new ConcurrentModificationException();
642 public final boolean hasNext() {
644 return nextKey != null;
648 public final void remove() {
650 checkState(currentKey != null);
651 final Integer offset = offsets.get(currentKey);
652 if (offset != null) {
654 objects[offset] = removedObject();
657 newIterator.remove();
660 expectedModCount = ++modCount;
664 protected final K nextKey() {
665 if (nextKey == null) {
666 throw new NoSuchElementException();
670 currentKey = nextKey;