/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.types; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; import java.util.AbstractMap; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.NavigableSet; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; import java.util.concurrent.ConcurrentNavigableMap; /** * A Map that keeps a sorted array in order to provide the concurrent map interface. * Keeping a sorted array means that it's much more cache line friendly, making reads faster * than the tree version. * * In order to make concurrent reads and writes safe this does a copy on write. * There can only be one concurrent write at a time. */ @InterfaceAudience.Private @InterfaceStability.Stable public class CopyOnWriteArrayMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, ConcurrentNavigableMap<K, V> { private final Comparator<? super K> keyComparator; private volatile ArrayHolder<K, V> holder; public CopyOnWriteArrayMap() { this(new Comparator<K>() { @Override public int compare(K o1, K o2) { return ((Comparable) o1).compareTo(o2); } }); } public CopyOnWriteArrayMap(final Comparator<? super K> keyComparator) { this.keyComparator = keyComparator; this.holder = new ArrayHolder<>(keyComparator, new Comparator<Entry<K, V>>() { @Override public int compare(Entry<K, V> o1, Entry<K, V> o2) { return keyComparator.compare(o1.getKey(), o2.getKey()); } }); } private CopyOnWriteArrayMap(final Comparator<? super K> keyComparator, ArrayHolder<K, V> holder) { this.keyComparator = keyComparator; this.holder = holder; } /* Un synchronized read operations. No locking. No waiting No copying. These should all be FAST. */ @Override public Comparator<? super K> comparator() { return keyComparator; } @Override public ConcurrentNavigableMap<K, V> tailMap(K fromKey, boolean inclusive) { ArrayHolder<K, V> current = this.holder; int index = current.find(fromKey); if (!inclusive && index >= 0) { index++; } else if (index < 0) { index = -(index + 1); } return new CopyOnWriteArrayMap<>( this.keyComparator, new ArrayHolder<>( current.entries, index, current.endIndex, current.keyComparator, current.comparator)); } @Override public ConcurrentNavigableMap<K, V> tailMap(K fromKey) { return this.tailMap(fromKey, true); } @Override public K firstKey() { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } return current.entries[current.startIndex].getKey(); } @Override public K lastKey() { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } return current.entries[current.endIndex - 1].getKey(); } @Override public Entry<K, V> lowerEntry(K key) { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } int index = current.find(key); // There's a key exactly equal. if (index >= 0) { index -= 1; } else { index = -(index + 1) - 1; } if (index < current.startIndex || index >= current.endIndex) { return null; } return current.entries[index]; } @Override public K lowerKey(K key) { Map.Entry<K, V> entry = lowerEntry(key); if (entry == null) { return null; } return entry.getKey(); } @Override public Entry<K, V> floorEntry(K key) { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } int index = current.find(key); if (index < 0) { index = -(index + 1) - 1; } if (index < current.startIndex || index >= current.endIndex) { return null; } return current.entries[index]; } @Override public K floorKey(K key) { Map.Entry<K, V> entry = floorEntry(key); if (entry == null) { return null; } return entry.getKey(); } @Override public Entry<K, V> ceilingEntry(K key) { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } int index = current.find(key); if (index < 0) { index = -(index + 1); } if (index < current.startIndex || index >= current.endIndex) { return null; } return current.entries[index]; } @Override public K ceilingKey(K key) { Map.Entry<K, V> entry = ceilingEntry(key); if (entry == null) { return null; } return entry.getKey(); } @Override public Entry<K, V> higherEntry(K key) { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } int index = current.find(key); // There's a key exactly equal. if (index >= 0) { index += 1; } else { index = -(index + 1); } if (index < current.startIndex || index >= current.endIndex) { return null; } return current.entries[index]; } @Override public K higherKey(K key) { Map.Entry<K, V> entry = higherEntry(key); if (entry == null) { return null; } return entry.getKey(); } @Override public Entry<K, V> firstEntry() { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } return current.entries[current.startIndex]; } @Override public Entry<K, V> lastEntry() { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } return current.entries[current.endIndex - 1]; } @Override public int size() { return holder.getLength(); } @Override public boolean isEmpty() { return holder.getLength() == 0; } @Override public boolean containsKey(Object key) { ArrayHolder<K, V> current = this.holder; int index = current.find((K) key); return index >= 0; } @Override public V get(Object key) { ArrayHolder<K, V> current = this.holder; int index = current.find((K) key); if (index >= 0) { return current.entries[index].getValue(); } return null; } @Override public NavigableSet<K> keySet() { return new ArrayKeySet<>(this.holder); } @Override public Collection<V> values() { return new ArrayValueCollection<>(this.holder); } @Override public Set<Entry<K, V>> entrySet() { return new ArrayEntrySet<>(this.holder); } /* Synchronized write methods. Every method should be synchronized. Only one modification at a time. These will be slow. */ @Override public synchronized V put(K key, V value) { ArrayHolder<K, V> current = this.holder; int index = current.find(key); COWEntry<K, V> newEntry = new COWEntry<>(key, value); if (index >= 0) { this.holder = current.replace(index, newEntry); return current.entries[index].getValue(); } else { this.holder = current.insert(-(index + 1), newEntry); } return null; } @Override public synchronized V remove(Object key) { ArrayHolder<K, V> current = this.holder; int index = current.find((K) key); if (index >= 0) { this.holder = current.remove(index); return current.entries[index].getValue(); } return null; } @Override public synchronized void clear() { this.holder = new ArrayHolder<>(this.holder.keyComparator, this.holder.comparator); } @Override public synchronized V putIfAbsent(K key, V value) { ArrayHolder<K, V> current = this.holder; int index = current.find(key); if (index < 0) { COWEntry<K, V> newEntry = new COWEntry<>(key, value); this.holder = current.insert(-(index + 1), newEntry); return value; } return current.entries[index].getValue(); } @Override public synchronized boolean remove(Object key, Object value) { ArrayHolder<K, V> current = this.holder; int index = current.find((K) key); if (index >= 0 && current.entries[index].getValue().equals(value)) { this.holder = current.remove(index); return true; } return false; } @Override public synchronized boolean replace(K key, V oldValue, V newValue) { ArrayHolder<K, V> current = this.holder; int index = current.find(key); if (index >= 0 && current.entries[index].getValue().equals(oldValue)) { COWEntry<K, V> newEntry = new COWEntry<>(key, newValue); this.holder = current.replace(index, newEntry); return true; } return false; } @Override public synchronized V replace(K key, V value) { ArrayHolder<K, V> current = this.holder; int index = current.find(key); if (index >= 0) { COWEntry<K, V> newEntry = new COWEntry<>(key, value); this.holder = current.replace(index, newEntry); return current.entries[index].getValue(); } return null; } @Override public Entry<K, V> pollFirstEntry() { throw new UnsupportedOperationException(); } @Override public Entry<K, V> pollLastEntry() { throw new UnsupportedOperationException(); } @Override public ConcurrentNavigableMap<K, V> descendingMap() { throw new UnsupportedOperationException(); } @Override public NavigableSet<K> navigableKeySet() { throw new UnsupportedOperationException(); } @Override public ConcurrentNavigableMap<K, V> subMap(K fromKey, K toKey) { throw new UnsupportedOperationException(); } @Override public ConcurrentNavigableMap<K, V> headMap(K toKey) { throw new UnsupportedOperationException(); } @Override public ConcurrentNavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { throw new UnsupportedOperationException(); } @Override public ConcurrentNavigableMap<K, V> headMap(K toKey, boolean inclusive) { throw new UnsupportedOperationException(); } @Override public NavigableSet<K> descendingKeySet() { throw new UnsupportedOperationException(); } private final class ArrayKeySet<K, V> implements NavigableSet<K> { private final ArrayHolder<K, V> holder; private ArrayKeySet(ArrayHolder<K, V> holder) { this.holder = holder; } @Override public int size() { return holder.getLength(); } @Override public boolean isEmpty() { return holder.getLength() == 0; } @Override public boolean contains(Object o) { ArrayHolder<K, V> current = this.holder; for (int i = current.startIndex; i < current.endIndex; i++) { if (current.entries[i].getValue().equals(o)) { return true; } } return false; } @Override public K lower(K k) { throw new UnsupportedOperationException(); } @Override public K floor(K k) { throw new UnsupportedOperationException(); } @Override public K ceiling(K k) { throw new UnsupportedOperationException(); } @Override public K higher(K k) { throw new UnsupportedOperationException(); } @Override public K pollFirst() { throw new UnsupportedOperationException(); } @Override public K pollLast() { throw new UnsupportedOperationException(); } @Override public Iterator<K> iterator() { return new ArrayKeyIterator<>(this.holder); } @Override public NavigableSet<K> descendingSet() { throw new UnsupportedOperationException(); } @Override public Iterator<K> descendingIterator() { throw new UnsupportedOperationException(); } @Override public NavigableSet<K> subSet(K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { throw new UnsupportedOperationException(); } @Override public NavigableSet<K> headSet(K toElement, boolean inclusive) { throw new UnsupportedOperationException(); } @Override public NavigableSet<K> tailSet(K fromElement, boolean inclusive) { throw new UnsupportedOperationException(); } @Override public Comparator<? super K> comparator() { return (Comparator<? super K>) keyComparator; } @Override public SortedSet<K> subSet(K fromElement, K toElement) { return null; } @Override public SortedSet<K> headSet(K toElement) { return null; } @Override public SortedSet<K> tailSet(K fromElement) { return null; } @Override public K first() { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } return current.entries[current.startIndex].getKey(); } @Override public K last() { ArrayHolder<K, V> current = this.holder; if (current.getLength() == 0) { return null; } return current.entries[current.endIndex - 1].getKey(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <T> T[] toArray(T[] a) { throw new UnsupportedOperationException(); } @Override public boolean add(K k) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends K> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } private final class ArrayValueCollection<K, V> implements Collection<V> { private final ArrayHolder<K, V> holder; private ArrayValueCollection(ArrayHolder<K, V> holder) { this.holder = holder; } @Override public int size() { return holder.getLength(); } @Override public boolean isEmpty() { return holder.getLength() == 0; } @Override public boolean contains(Object o) { throw new UnsupportedOperationException(); } @Override public Iterator<V> iterator() { return new ArrayValueIterator<>(this.holder); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <T> T[] toArray(T[] a) { throw new UnsupportedOperationException(); } @Override public boolean add(V v) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends V> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="EQ_ALWAYS_FALSE", justification="Intentional") public boolean equals(Object o) { return false; // FindBugs: Causes EQ_ALWAYS_FALSE. Suppressed. } @Override public int hashCode() { return 0; } } private final class ArrayKeyIterator<K, V> implements Iterator<K> { int index; private final ArrayHolder<K, V> holder; private ArrayKeyIterator(ArrayHolder<K, V> holder) { this.holder = holder; index = holder.startIndex; } @Override public boolean hasNext() { return index < holder.endIndex; } @Override public K next() { return holder.entries[index++].getKey(); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } private final class ArrayValueIterator<K, V> implements Iterator<V> { int index; private final ArrayHolder<K, V> holder; private ArrayValueIterator(ArrayHolder<K, V> holder) { this.holder = holder; index = holder.startIndex; } @Override public boolean hasNext() { return index < holder.endIndex; } @Override public V next() { return holder.entries[index++].getValue(); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } private final class ArrayEntryIterator<K, V> implements Iterator<Map.Entry<K, V>> { int index; private final ArrayHolder<K, V> holder; private ArrayEntryIterator(ArrayHolder<K, V> holder) { this.holder = holder; this.index = holder.startIndex; } @Override public boolean hasNext() { return index < holder.endIndex; } @Override @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="IT_NO_SUCH_ELEMENT", justification="Intentional") public Entry<K, V> next() { if (!hasNext()) { throw new NoSuchElementException(); } return holder.entries[index++]; } @Override public void remove() { throw new UnsupportedOperationException("remove"); } } private final class ArrayEntrySet<K, V> implements Set<Map.Entry<K, V>> { private final ArrayHolder<K, V> holder; private ArrayEntrySet(ArrayHolder<K, V> holder) { this.holder = holder; } @Override public int size() { return holder.getLength(); } @Override public boolean isEmpty() { return holder.getLength() == 0; } @Override public boolean contains(Object o) { return false; } @Override public Iterator<Entry<K, V>> iterator() { return new ArrayEntryIterator<>(this.holder); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <T> T[] toArray(T[] a) { throw new UnsupportedOperationException(); } @Override public boolean add(Entry<K, V> kvEntry) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends Entry<K, V>> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } private final static class ArrayHolder<K, V> { private final COWEntry<K, V>[] entries; private final int startIndex; private final int endIndex; private final Comparator<? super K> keyComparator; private final Comparator<Map.Entry<K, V>> comparator; int getLength() { return endIndex - startIndex; } /** * Binary search for a given key * @param needle The key to look for in all of the entries * @return Same return value as Arrays.binarySearch. * Positive numbers mean the index. * Otherwise (-1 * insertion point) - 1 */ int find(K needle) { int begin = startIndex; int end = endIndex - 1; while (begin <= end) { int mid = begin + ((end - begin) / 2); K midKey = entries[ mid].key; int compareRes = keyComparator.compare(midKey, needle); // 0 means equals // We found the key. if (compareRes == 0) { return mid; } else if (compareRes < 0) { // midKey is less than needle so we need // to look at farther up begin = mid + 1; } else { // midKey is greater than needle so we // need to look down. end = mid - 1; } } return (-1 * begin) - 1; } ArrayHolder<K, V> replace(int index, COWEntry<K, V> newEntry) { // TODO should this restart the array back at start index 0 ? COWEntry<K, V>[] newEntries = entries.clone(); newEntries[index] = newEntry; return new ArrayHolder<>(newEntries, startIndex, endIndex, keyComparator, comparator); } ArrayHolder<K, V> remove(int index) { COWEntry<K,V>[] newEntries = new COWEntry[getLength() - 1]; System.arraycopy(this.entries, startIndex, newEntries, 0, index - startIndex); System.arraycopy(this.entries, index + 1, newEntries, index, entries.length - index - 1); return new ArrayHolder<>(newEntries, 0, newEntries.length, keyComparator, comparator); } ArrayHolder<K, V> insert(int index, COWEntry<K, V> newEntry) { COWEntry<K,V>[] newEntries = new COWEntry[getLength() + 1]; System.arraycopy(this.entries, startIndex, newEntries, 0, index - startIndex); newEntries[index] = newEntry; System.arraycopy(this.entries, index, newEntries, index + 1, getLength() - index); return new ArrayHolder<>(newEntries, 0, newEntries.length, keyComparator, comparator); } private ArrayHolder( final Comparator<? super K> keyComparator, final Comparator<Map.Entry<K, V>> comparator) { this.endIndex = 0; this.startIndex = 0; this.entries = new COWEntry[] {}; this.keyComparator = keyComparator; this.comparator = comparator; } private ArrayHolder(COWEntry[] entries, int startIndex, int endIndex, final Comparator<? super K> keyComparator, Comparator<Map.Entry<K, V>> comparator) { this.entries = entries; this.startIndex = startIndex; this.endIndex = endIndex; this.keyComparator = keyComparator; this.comparator = comparator; } } private final static class COWEntry<K, V> implements Map.Entry<K, V> { K key = null; V value = null; COWEntry(K key, V value) { this.key = key; this.value = value; } @Override public K getKey() { return key; } @Override public V getValue() { return value; } @Override public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } } }