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.Collection;
20 import java.util.ConcurrentModificationException;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.LinkedHashMap;
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 private static final Object[] EMPTY_ARRAY = new Object[0];
60 private final Map<K, Integer> offsets;
61 private HashMap<K, V> newKeys;
63 private int removed = 0;
64 private transient volatile int modCount;
65 private boolean needClone = true;
67 MutableOffsetMap(final Map<K, Integer> offsets, final V[] objects, final HashMap<K, V> newKeys) {
68 Verify.verify(newKeys.isEmpty());
69 this.offsets = Preconditions.checkNotNull(offsets);
70 this.objects = Preconditions.checkNotNull(objects);
71 this.newKeys = Preconditions.checkNotNull(newKeys);
74 @SuppressWarnings("unchecked")
75 MutableOffsetMap(final HashMap<K, V> newKeys) {
76 this(ImmutableMap.<K, Integer>of(), (V[]) EMPTY_ARRAY, newKeys);
79 @SuppressWarnings("unchecked")
80 MutableOffsetMap(final Map<K, Integer> offsets, final Map<K, V> source, final HashMap<K, V> newKeys) {
81 this(offsets, (V[]) new Object[offsets.size()], newKeys);
83 for (Entry<K, V> e : source.entrySet()) {
84 objects[offsets.get(e.getKey())] = Preconditions.checkNotNull(e.getValue());
87 this.needClone = false;
90 public static <K, V> MutableOffsetMap<K, V> copyOf(final Map<K, V> m) {
91 if (m instanceof MutableOffsetMap) {
92 return ((MutableOffsetMap<K, V>) m).clone();
94 if (m instanceof ImmutableOffsetMap) {
95 final ImmutableOffsetMap<K, V> om = (ImmutableOffsetMap<K, V>) m;
96 return new MutableOffsetMap.Ordered<>(om.offsets(), om.objects());
99 return new MutableOffsetMap.Ordered<>(m);
102 public static <K, V> MutableOffsetMap<K, V> of() {
103 return new MutableOffsetMap.Ordered<>();
107 public final int size() {
108 return offsets.size() + newKeys.size() - removed;
112 public final boolean isEmpty() {
117 public final boolean containsKey(final Object key) {
118 final Integer offset = offsets.get(key);
119 return offset == null ? newKeys.containsKey(key) : objects[offset] != null;
123 public final V get(final Object key) {
124 final Integer offset = offsets.get(key);
125 return offset == null ? newKeys.get(key) : objects[offset];
128 private void cloneArray() {
131 if (!EMPTY_ARRAY.equals(objects)) {
132 objects = objects.clone();
138 public V put(final K key, final V value) {
139 Preconditions.checkNotNull(value);
140 final Integer offset = offsets.get(Preconditions.checkNotNull(key));
141 if (offset == null) {
142 final V ret = newKeys.put(key, value);
150 final V ret = objects[offset];
151 objects[offset] = value;
161 public V remove(final Object key) {
162 final Integer offset = offsets.get(key);
163 if (offset == null) {
164 final V ret = newKeys.remove(key);
172 final V ret = objects[offset];
173 objects[offset] = null;
182 public void clear() {
186 Arrays.fill(objects, null);
187 removed = objects.length;
193 public final Set<Entry<K, V>> entrySet() {
194 return new EntrySet();
198 public Map<K, V> toUnmodifiableMap() {
199 if (newKeys.isEmpty() && removed == 0) {
200 // Make sure next modification clones the array, as we leak it to the map we return.
203 * TODO: we could track the ImmutableOffsetMap from which this one was instantiated and if we do not
204 * perform any modifications, just return the original instance. The trade-off is increased complexity
205 * and an additional field in this class.
207 return new ImmutableOffsetMap.Ordered<>(offsets, objects);
210 final int s = size();
212 return ImmutableMap.of();
215 return ImmutableMap.copyOf(this);
218 // Construct the set of keys
219 final Collection<K> keyset = new ArrayList<>(s);
221 if (removed != offsets.size()) {
222 for (Entry<K, Integer> e : offsets.entrySet()) {
223 if (objects[e.getValue()] != null) {
224 keyset.add(e.getKey());
229 keyset.addAll(offsets.keySet());
231 keyset.addAll(newKeys.keySet());
233 // Construct the values
234 @SuppressWarnings("unchecked")
235 final V[] values = (V[])new Object[keyset.size()];
238 if (removed != offsets.size()) {
239 for (Entry<K, Integer> e : offsets.entrySet()) {
240 final V o = objects[e.getValue()];
247 System.arraycopy(objects, 0, values, 0, offsets.size());
250 for (V v : newKeys.values()) {
254 return new ImmutableOffsetMap.Ordered<>(OffsetMapCache.orderedOffsets(keyset), values);
257 @SuppressWarnings("unchecked")
259 public MutableOffsetMap<K, V> clone() {
260 final MutableOffsetMap<K, V> ret;
263 ret = (MutableOffsetMap<K, V>) super.clone();
264 } catch (CloneNotSupportedException e) {
265 throw new IllegalStateException("Clone is expected to work", e);
268 ret.newKeys = (HashMap<K, V>) newKeys.clone();
269 ret.needClone = true;
274 public final int hashCode() {
277 for (Entry<K, Integer> e : offsets.entrySet()) {
278 final Object v = objects[e.getValue()];
280 result += e.getKey().hashCode() ^ v.hashCode();
284 return result + newKeys.hashCode();
288 public final boolean equals(final Object o) {
292 if (!(o instanceof Map)) {
296 if (o instanceof ImmutableOffsetMap) {
297 final ImmutableOffsetMap<?, ?> om = (ImmutableOffsetMap<?, ?>) o;
299 if (newKeys.isEmpty() && offsets.equals(om.offsets())) {
300 return Arrays.deepEquals(objects, om.objects());
302 } else if (o instanceof MutableOffsetMap) {
303 final MutableOffsetMap<?, ?> om = (MutableOffsetMap<?, ?>) o;
305 if (offsets.equals(om.offsets)) {
306 return Arrays.deepEquals(objects, om.objects) && newKeys.equals(om.newKeys);
310 // Fall back to brute map compare
311 final Map<?, ?> other = (Map<?, ?>)o;
313 // Size and key sets have to match
314 if (size() != other.size() || !keySet().equals(other.keySet())) {
319 // Ensure all newKeys are present. Note newKeys is guaranteed to
320 // not contain null value.
321 for (Entry<K, V> e : newKeys.entrySet()) {
322 if (!e.getValue().equals(other.get(e.getKey()))) {
327 // Ensure all objects are present
328 for (Entry<K, Integer> e : offsets.entrySet()) {
329 final V v = objects[e.getValue()];
330 if (v != null && !v.equals(other.get(e.getKey()))) {
334 } catch (ClassCastException e) {
335 // Can be thrown by other.get() and indicate we have incompatible key types
343 public final Set<K> keySet() {
348 final boolean needClone() {
353 final Object array() {
358 final Object newKeys() {
362 private final class EntrySet extends AbstractSet<Entry<K, V>> {
364 public Iterator<Entry<K, V>> iterator() {
365 return new AbstractSetIterator<Entry<K, V>>() {
367 public Entry<K, V> next() {
368 final K key = nextKey();
369 return new SimpleEntry<>(key, get(key));
376 return MutableOffsetMap.this.size();
380 public boolean contains(final Object o) {
381 if (!(o instanceof Entry)) {
385 @SuppressWarnings("unchecked")
386 final Entry<K,V> e = (Entry<K,V>) o;
387 if (e.getValue() == null) {
391 return e.getValue().equals(MutableOffsetMap.this.get(e.getKey()));
395 public boolean add(final Entry<K, V> e) {
396 Preconditions.checkNotNull(e.getValue());
397 final V p = MutableOffsetMap.this.put(e.getKey(), e.getValue());
398 return !e.getValue().equals(p);
402 public boolean remove(final Object o) {
403 if (!(o instanceof Entry)) {
407 @SuppressWarnings("unchecked")
408 final Entry<K,V> e = (Entry<K,V>) o;
409 if (e.getValue() == null) {
413 final V v = MutableOffsetMap.this.get(e.getKey());
414 if (e.getValue().equals(v)) {
415 MutableOffsetMap.this.remove(e.getKey());
422 public void clear() {
423 MutableOffsetMap.this.clear();
427 private final class KeySet extends AbstractSet<K> {
429 public Iterator<K> iterator() {
430 return new AbstractSetIterator<K>() {
440 return MutableOffsetMap.this.size();
444 private abstract class AbstractSetIterator<E> implements Iterator<E> {
445 private final Iterator<Entry<K, Integer>> oldIterator = offsets.entrySet().iterator();
446 private final Iterator<K> newIterator = newKeys.keySet().iterator();
447 private int expectedModCount = modCount;
448 private K currentKey, nextKey;
450 AbstractSetIterator() {
454 private void updateNextKey() {
455 while (oldIterator.hasNext()) {
456 final Entry<K, Integer> e = oldIterator.next();
457 if (objects[e.getValue()] != null) {
458 nextKey = e.getKey();
463 nextKey = newIterator.hasNext() ? newIterator.next() : null;
466 private void checkModCount() {
467 if (modCount != expectedModCount) {
468 throw new ConcurrentModificationException();
473 public final boolean hasNext() {
475 return nextKey != null;
479 public final void remove() {
480 Preconditions.checkState(currentKey != null);
483 final Integer offset = offsets.get(currentKey);
484 if (offset != null) {
486 objects[offset] = null;
489 newIterator.remove();
492 expectedModCount = ++modCount;
496 protected final K nextKey() {
497 if (nextKey == null) {
498 throw new NoSuchElementException();
502 currentKey = nextKey;