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.verifyNotNull;
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.Collections;
23 import java.util.ConcurrentModificationException;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.LinkedHashMap;
27 import java.util.List;
29 import java.util.NoSuchElementException;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.eclipse.jdt.annotation.Nullable;
35 * A mutable version of {@link ImmutableOffsetMap}. It inherits the set of mappings from the immutable version and
36 * allows updating/removing existing mappings. New mappings are stored in a dedicated {@link LinkedHashMap} to preserve
37 * insertion order. It also tracks the need to duplicate the backing array, so the sequence of
39 * ImmutableOffsetMap<K, V> source;
40 * ImmutableOffsetMap<K, V> result = source.createMutableClone().immutableCopy();
42 * results in source and result sharing the backing objects.
44 * <p>This map does not support null keys nor values.
46 * @param <K> the type of keys maintained by this map
47 * @param <V> the type of mapped values
50 public abstract class MutableOffsetMap<K, V> extends AbstractMap<K, V> implements Cloneable, ModifiableMapPhase<K, V> {
51 static final class Ordered<K, V> extends MutableOffsetMap<K, V> {
55 Ordered(final Map<K, V> source) {
56 super(OffsetMapCache.orderedOffsets(source.keySet()), source);
59 Ordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
60 super(offsets, objects);
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 HashMap<K, V> createNewKeys() {
85 return new LinkedHashMap<>();
89 static final class Unordered<K, V> extends MutableOffsetMap<K, V> {
93 Unordered(final Map<K, V> source) {
94 super(OffsetMapCache.unorderedOffsets(source.keySet()), source);
97 Unordered(final ImmutableMap<K, Integer> offsets, final V[] objects) {
98 super(offsets, objects);
102 Object removedObject() {
107 UnmodifiableMapPhase<K, V> modifiedMap(final List<K> keys, final V[] values) {
108 final ImmutableMap<K, Integer> offsets = OffsetMapCache.unorderedOffsets(keys);
109 return new ImmutableOffsetMap.Unordered<>(offsets, OffsetMapCache.adjustedArray(offsets, keys, values));
113 UnmodifiableMapPhase<K, V> unmodifiedMap(final ImmutableMap<K, Integer> offsetMap, final V[] values) {
114 return new ImmutableOffsetMap.Unordered<>(offsetMap, values);
118 SharedSingletonMap<K, V> singletonMap() {
119 return SharedSingletonMap.unorderedCopyOf(this);
123 HashMap<K, V> createNewKeys() {
124 return new HashMap<>();
128 private static final Object[] EMPTY_ARRAY = new Object[0];
129 private static final Object REMOVED = new Object();
131 private final ImmutableMap<K, Integer> offsets;
132 private HashMap<K, V> newKeys;
133 private Object[] objects;
134 private int removed = 0;
136 // Fail-fast iterator guard, see java.util.ArrayList for reference.
137 @SuppressFBWarnings("VO_VOLATILE_INCREMENT")
138 private transient volatile int modCount;
139 private boolean needClone = true;
141 MutableOffsetMap(final ImmutableMap<K, Integer> offsets, final Object[] objects) {
142 this.offsets = requireNonNull(offsets);
143 this.objects = requireNonNull(objects);
147 this(ImmutableMap.of(), EMPTY_ARRAY);
150 MutableOffsetMap(final ImmutableMap<K, Integer> offsets, final Map<K, V> source) {
151 this(offsets, new Object[offsets.size()]);
153 for (Entry<K, V> e : source.entrySet()) {
154 objects[verifyNotNull(offsets.get(e.getKey()))] = requireNonNull(e.getValue());
157 this.needClone = false;
161 * Create a {@link MutableOffsetMap} of the specified map, retaining its iteration order.
163 * @param map input map
164 * @return MutableOffsetMap with the same iteration order
165 * @throws NullPointerException if {@code map} is null
167 public static <K, V> @NonNull MutableOffsetMap<K, V> orderedCopyOf(final Map<K, V> map) {
168 if (map instanceof Ordered) {
169 return ((Ordered<K, V>) map).clone();
171 if (map instanceof ImmutableOffsetMap) {
172 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) map;
173 return new Ordered<>(om.offsets(), om.objects());
176 return new Ordered<>(map);
180 * Create a {@link MutableOffsetMap} of the specified map, potentially with a different iteration order.
182 * @param map input map
183 * @return MutableOffsetMap with undefined iteration order
184 * @throws NullPointerException if {@code map} is null
186 public static <K, V> @NonNull MutableOffsetMap<K, V> unorderedCopyOf(final Map<K, V> map) {
187 if (map instanceof Unordered) {
188 return ((Unordered<K, V>) map).clone();
190 if (map instanceof ImmutableOffsetMap) {
191 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) map;
192 return new Unordered<>(om.offsets(), om.objects());
195 return new Unordered<>(map);
199 * Create an empty {@link MutableOffsetMap} which has an iteration order matching the insertion order.
201 * @return MutableOffsetMap which preserves insertion order
203 public static <K, V> @NonNull MutableOffsetMap<K, V> ordered() {
204 return new MutableOffsetMap.Ordered<>();
208 * Create an empty {@link MutableOffsetMap} which has unspecified iteration order.
210 * @return An MutableOffsetMap
212 public static <K, V> @NonNull MutableOffsetMap<K, V> unordered() {
213 return new MutableOffsetMap.Unordered<>();
216 abstract Object removedObject();
218 abstract UnmodifiableMapPhase<K, V> modifiedMap(List<K> keys, V[] values);
220 abstract UnmodifiableMapPhase<K, V> unmodifiedMap(ImmutableMap<K, Integer> offsetMap, V[] values);
222 abstract SharedSingletonMap<K, V> singletonMap();
225 public final int size() {
226 return offsets.size() - removed + (newKeys == null ? 0 : newKeys.size());
230 public final boolean isEmpty() {
235 public final boolean containsKey(final Object key) {
236 final Integer offset = offsets.get(key);
237 if (offset != null) {
238 final Object obj = objects[offset];
239 if (!REMOVED.equals(obj)) {
244 return newKeys != null && newKeys.containsKey(key);
248 public final V get(final Object key) {
249 final Integer offset = offsets.get(key);
250 if (offset != null) {
251 final Object obj = objects[offset];
254 * This is a bit tricky: Ordered will put REMOVED to removed objects to retain strict insertion order.
255 * Unordered will add null, indicating that the slot may be reused in future. Hence if we see a REMOVED
256 * marker, we need to fall back to checking with new keys.
258 if (!REMOVED.equals(obj)) {
259 @SuppressWarnings("unchecked")
260 final V ret = (V)obj;
265 return newKeys == null ? null : newKeys.get(key);
268 private void cloneArray() {
271 if (objects.length != 0) {
272 objects = objects.clone();
278 public final V put(final K key, final V value) {
279 requireNonNull(value);
280 final Integer offset = offsets.get(requireNonNull(key));
281 if (offset != null) {
282 final Object obj = objects[offset];
285 * Put which can potentially replace something in objects. Replacing an object does not cause iterators
286 * to be invalidated and does follow insertion order (since it is not a fresh insert). If the object has
287 * been removed, we fall back to newKeys.
289 if (!REMOVED.equals(obj)) {
290 @SuppressWarnings("unchecked")
291 final V ret = (V)obj;
294 objects[offset] = value;
304 if (newKeys == null) {
305 newKeys = createNewKeys();
307 final V ret = newKeys.put(key, value);
315 public final V remove(final Object key) {
316 final Integer offset = offsets.get(key);
317 if (offset != null) {
318 final Object obj = objects[offset];
321 * A previous remove() may have indicated that the objects slot cannot be reused. In that case we need
322 * to fall back to checking with newKeys.
324 if (!REMOVED.equals(obj)) {
327 @SuppressWarnings("unchecked")
328 final V ret = (V)obj;
329 objects[offset] = removedObject();
338 if (newKeys == null) {
341 final V ret = newKeys.remove(key);
349 public final void clear() {
351 if (newKeys != null) {
355 Arrays.fill(objects, removedObject());
356 removed = objects.length;
362 public final @NonNull Set<Entry<K, V>> entrySet() {
363 return new EntrySet();
367 public @NonNull Map<K, V> toUnmodifiableMap() {
368 if (removed == 0 && noNewKeys()) {
369 // Make sure next modification clones the array, as we leak it to the map we return.
372 // We have ended up with no removed objects, hence this cast is safe
373 @SuppressWarnings("unchecked")
374 final V[] values = (V[])objects;
377 * TODO: we could track the ImmutableOffsetMap from which this one was instantiated and if we do not
378 * perform any modifications, just return the original instance. The trade-off is increased complexity
379 * and an additional field in this class.
381 return unmodifiedMap(offsets, values);
384 final int s = size();
386 return ImmutableMap.of();
389 return singletonMap();
392 // Construct the set of keys
393 final List<K> keyset = new ArrayList<>(s);
395 if (removed != offsets.size()) {
396 for (Entry<K, Integer> e : offsets.entrySet()) {
397 final Object o = objects[e.getValue()];
398 if (o != null && !REMOVED.equals(o)) {
399 keyset.add(e.getKey());
404 keyset.addAll(offsets.keySet());
406 if (newKeys != null) {
407 keyset.addAll(newKeys.keySet());
410 // Construct the values
411 @SuppressWarnings("unchecked")
412 final V[] values = (V[])new Object[keyset.size()];
415 if (removed != offsets.size()) {
416 for (Entry<K, Integer> e : offsets.entrySet()) {
417 final Object o = objects[e.getValue()];
418 if (o != null && !REMOVED.equals(o)) {
419 @SuppressWarnings("unchecked")
421 values[offset++] = v;
426 System.arraycopy(objects, 0, values, 0, offsets.size());
427 offset = offsets.size();
429 if (newKeys != null) {
430 for (V v : newKeys.values()) {
431 values[offset++] = v;
435 return modifiedMap(keyset, values);
438 @SuppressWarnings("unchecked")
440 public MutableOffsetMap<K, V> clone() {
441 final MutableOffsetMap<K, V> ret;
444 ret = (MutableOffsetMap<K, V>) super.clone();
445 } catch (CloneNotSupportedException e) {
446 throw new IllegalStateException("Clone is expected to work", e);
449 ret.newKeys = newKeys == null ? null : (HashMap<K, V>) newKeys.clone();
450 ret.needClone = true;
455 public final int hashCode() {
458 for (Entry<K, Integer> e : offsets.entrySet()) {
459 final Object v = objects[e.getValue()];
461 result += e.getKey().hashCode() ^ v.hashCode();
465 return newKeys != null ? result + newKeys.hashCode() : result;
469 public final boolean equals(final Object obj) {
473 if (!(obj instanceof Map)) {
477 if (obj instanceof ImmutableOffsetMap) {
478 final ImmutableOffsetMap<?, ?> om = (ImmutableOffsetMap<?, ?>) obj;
480 if (noNewKeys() && offsets.equals(om.offsets())) {
481 return Arrays.deepEquals(objects, om.objects());
483 } else if (obj instanceof MutableOffsetMap) {
484 final MutableOffsetMap<?, ?> om = (MutableOffsetMap<?, ?>) obj;
486 if (offsets.equals(om.offsets)) {
487 return Arrays.deepEquals(objects, om.objects) && equalNewKeys(om);
491 // Fall back to brute map compare
492 return mapEquals((Map<?, ?>)obj);
495 private boolean equalNewKeys(final MutableOffsetMap<?, ?> other) {
496 return noNewKeys() ? other.noNewKeys() : newKeys.equals(other.newKeys());
499 private boolean mapEquals(final Map<?, ?> other) {
500 // Size and key sets have to match
501 if (size() != other.size() || !keySet().equals(other.keySet())) {
506 if (newKeys != null) {
507 // Ensure all newKeys are present. Note newKeys is guaranteed to not contain a null value.
508 for (Entry<K, V> e : newKeys.entrySet()) {
509 if (!e.getValue().equals(other.get(e.getKey()))) {
515 // Ensure all objects are present
516 for (Entry<K, Integer> e : offsets.entrySet()) {
517 final Object val = objects[e.getValue()];
518 if (val != null && !REMOVED.equals(val) && !val.equals(other.get(e.getKey()))) {
522 } catch (ClassCastException e) {
523 // Can be thrown by other.get() and indicate we have incompatible key types
531 public final @NonNull Set<K> keySet() {
536 final boolean needClone() {
541 final Object array() {
546 final Object newKeys() {
547 return newKeys != null ? newKeys : ImmutableMap.of();
550 abstract HashMap<K, V> createNewKeys();
552 private boolean noNewKeys() {
553 return newKeys == null || newKeys.isEmpty();
556 private final class EntrySet extends AbstractSet<Entry<K, V>> {
558 public @NonNull Iterator<Entry<K, V>> iterator() {
559 return new AbstractSetIterator<>() {
561 public Entry<K, V> next() {
562 final K key = nextKey();
563 return new SimpleEntry<>(key, get(key));
570 return MutableOffsetMap.this.size();
574 @SuppressWarnings("checkstyle:parameterName")
575 public boolean contains(final Object o) {
576 if (!(o instanceof Entry)) {
580 @SuppressWarnings("unchecked")
581 final Entry<K,V> e = (Entry<K,V>) o;
582 if (e.getValue() == null) {
586 return e.getValue().equals(MutableOffsetMap.this.get(e.getKey()));
590 @SuppressWarnings("checkstyle:parameterName")
591 public boolean add(final Entry<K, V> e) {
592 final V v = requireNonNull(e.getValue());
593 final V p = MutableOffsetMap.this.put(e.getKey(), v);
598 @SuppressWarnings("checkstyle:parameterName")
599 public boolean remove(final Object o) {
600 if (!(o instanceof Entry)) {
604 @SuppressWarnings("unchecked")
605 final Entry<K,V> e = (Entry<K,V>) o;
606 if (e.getValue() == null) {
610 final V v = MutableOffsetMap.this.get(e.getKey());
611 if (e.getValue().equals(v)) {
612 MutableOffsetMap.this.remove(e.getKey());
619 public void clear() {
620 MutableOffsetMap.this.clear();
624 private final class KeySet extends AbstractSet<K> {
626 public @NonNull Iterator<K> iterator() {
627 return new AbstractSetIterator<>() {
637 return MutableOffsetMap.this.size();
641 private abstract class AbstractSetIterator<E> implements Iterator<E> {
642 private final Iterator<Entry<K, Integer>> oldIterator = offsets.entrySet().iterator();
643 private final Iterator<K> newIterator = newKeys == null ? Collections.emptyIterator()
644 : newKeys.keySet().iterator();
645 private int expectedModCount = modCount;
646 private @Nullable K currentKey = null;
647 private @Nullable K nextKey;
649 AbstractSetIterator() {
653 private void updateNextKey() {
654 while (oldIterator.hasNext()) {
655 final Entry<K, Integer> e = oldIterator.next();
656 final Object obj = objects[e.getValue()];
657 if (obj != null && !REMOVED.equals(obj)) {
658 nextKey = e.getKey();
663 nextKey = newIterator.hasNext() ? newIterator.next() : null;
666 private void checkModCount() {
667 if (modCount != expectedModCount) {
668 throw new ConcurrentModificationException();
673 public final boolean hasNext() {
675 return nextKey != null;
679 public final void remove() {
681 checkState(currentKey != null);
682 final Integer offset = offsets.get(currentKey);
683 if (offset != null) {
685 objects[offset] = removedObject();
688 newIterator.remove();
691 expectedModCount = ++modCount;
695 protected final K nextKey() {
696 if (nextKey == null) {
697 throw new NoSuchElementException();
701 currentKey = nextKey;