/* * Copyright 2015 McDowell * * Licensed 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 uk.kludje.experimental.collect.array; import uk.kludje.experimental.array.EmptyArrays; import uk.kludje.experimental.array.LinearSearch; import uk.kludje.experimental.collect.MutableSparseArray; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.stream.IntStream; /** * Created by user on 12/12/15. */ final class ArrayBackedMutableSparseArray<V> implements MutableSparseArray<V> { private int[] keys = EmptyArrays.EMPTY_INT_ARRAY; protected Object[] elements = EmptyArrays.EMPTY_OBJECT_ARRAY; protected int start; protected int end; protected int version; @Override public V put(int key, V value) { assert value != this: "value != this"; // first check if this is a replace operation int index = LinearSearch.find(keys, start, end, key); if (index != LinearSearch.NOT_FOUND) { @SuppressWarnings("unchecked") V old = (V) elements[index]; elements[index] = value; return old; } // add version++; ensureFreeCapacity(1); if (start > 0) { start--; keys[start] = key; elements[start] = value; } else { keys[end] = key; elements[end] = value; end++; } return null; } protected int growSizeBy(int requiredCapacity) { assert requiredCapacity >= 0 : "requiredCapacity >= 0"; int size = size(); int standardIncrease = (size == 0) ? 8 : size; if (requiredCapacity > standardIncrease) { return requiredCapacity; } else { return standardIncrease; } } protected void ensureFreeCapacity(int growBy) { assert growBy >= 0: "growBy >= 0"; version++; if (start > growBy) { return; } int endCapacity = elements.length - end; if (growBy <= endCapacity) { return; } int size = size(); int capacity = endCapacity + start; if (growBy <= capacity) { System.arraycopy(keys, start, keys, 0, size); System.arraycopy(elements, start, elements, 0, size); start = 0; end = size; return; } int increase = growSizeBy(growBy); int newSize = increase + size; assert newSize >= (size + growBy): "newSize >= (size + growBy)"; int[] newKeys = new int[newSize]; Object[] newValues = new Object[newSize]; System.arraycopy(keys, start, newKeys, 0, size); System.arraycopy(elements, start, newValues, 0, size); start = 0; end = size; keys = newKeys; elements = newValues; } @Override public boolean remove(int key) { int index = LinearSearch.find(keys, start, end, key); if (index == LinearSearch.NOT_FOUND) { return false; } // found version++; if (index == start) { start++; elements[index] = null; } else if (index == (end - 1)) { end--; elements[index] = null; } else { System.arraycopy(keys, index + 1, keys, index, end - index); System.arraycopy(elements, index + 1, elements, index, end - index); end--; elements[index] = null; } return true; } @Override public V get(int key) { int index = LinearSearch.find(keys, start, end, key); @SuppressWarnings("unchecked") V value = (index == LinearSearch.NOT_FOUND) ? null : (V) elements[index]; return value; } @Override public int size() { return end - start; } @Override public boolean contains(int key) { return LinearSearch.find(keys, start, end, key) != LinearSearch.NOT_FOUND; } @Override public IntStream keyStream() { return IntStream.of(keys) .skip(start) .limit(size()); } public int getVersion() { return version; } protected void checkVersion(int version) { if (version != this.version) { String threadName = Thread.currentThread().getName(); throw new ConcurrentModificationException(threadName); } } @Override public boolean isEmpty() { return end == start; } @Override public Iterator<SparseArrayEntry<V>> iterator() { class SparseArrayIterator implements Iterator<SparseArrayEntry<V>> { private int version = getVersion(); private int index = 0; @Override public boolean hasNext() { checkVersion(version); return index < size(); } @Override public SparseArrayEntry<V> next() { if (!hasNext()) { throw new NoSuchElementException(String.valueOf(index)); } int key = keys[index + start]; @SuppressWarnings("unchecked") V value = (V) elements[index + start]; return SparseArrayEntry.entry(key, value); } @Override public void remove() { checkVersion(version); int indexToRemove = start + index--; ArrayBackedMutableSparseArray.this.remove(indexToRemove); version = getVersion(); } } return new SparseArrayIterator(); } public static <V> MutableSparseArray<V> sparseArray() { return new ArrayBackedMutableSparseArray<>(); } }