/******************************************************************************* * Copyright 2015 Analog Devices, Inc. * * 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 com.analog.lyric.collect; import java.io.Serializable; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.RandomAccess; import org.eclipse.jdt.annotation.NonNullByDefault; /** * A random access list with unique elements and fast lookup. * <p> * This maintains an array of values and a map of each value to its * index in the array. * <p> * @since 0.08 * @author Christopher Barber */ @NonNullByDefault(false) public class IndexedArrayList<T> extends AbstractList<T> implements RandomAccess, Serializable { private static final long serialVersionUID = 1L; private final ArrayList<T> _array; private final HashMap<T,Integer> _valueToIndex; /*-------------- * Construction */ public IndexedArrayList(int capacity) { _array = new ArrayList<>(capacity); _valueToIndex = new HashMap<>(capacity); } public IndexedArrayList() { this(16); } public IndexedArrayList(Collection<T> collection) { this(collection.size()); addAll(collection); } /*-------------------- * Collection methods */ @Override public boolean add(T var) { if (_valueToIndex.containsKey(var)) { return false; } _valueToIndex.put(var, _array.size()); _array.add(var); return true; } @Override public void clear() { _array.clear(); _valueToIndex.clear(); } @Override public boolean contains(Object obj) { return _valueToIndex.containsKey(obj); } @Override public boolean remove(Object obj) { Integer removedIndex = _valueToIndex.remove(obj); if (removedIndex == null) { return false; } int i = removedIndex; _array.remove(i); recomputeValueToIndex(removedIndex); return true; } @Override public T remove(int index) { T var = _array.remove(index); _valueToIndex.remove(var); recomputeValueToIndex(index); return var; } @Override public boolean removeAll(Collection<?> collection) { if (_array.removeAll(collection)) { _valueToIndex.clear(); recomputeValueToIndex(0); return true; } return false; } @Override public boolean retainAll(Collection<?> collection) { if (_array.retainAll(collection)) { _valueToIndex.clear(); recomputeValueToIndex(0); return true; } return false; } @Override public int size() { return _array.size(); } /*-------------- * List methods */ @Override public T get(int index) { return _array.get(index); } @Override public int indexOf(Object obj) { Integer index = _valueToIndex.get(obj); return index != null ? index : -1; } @Override public int lastIndexOf(Object obj) { return indexOf(obj); } @Override public T set(int index, T newValue) { T oldValue = _array.set(index, newValue); if (!oldValue.equals(newValue)) { _valueToIndex.remove(oldValue); Integer oldIndex = _valueToIndex.put(newValue, index); if (oldIndex != null) { // Value was already in array in a different position. int i = oldIndex; remove(i); } } return oldValue; } /*----------------- * Private methods */ private void recomputeValueToIndex(int minIndex) { for (int i = minIndex, size = _array.size(); i < size; ++i) { _valueToIndex.put(_array.get(i), i); } } }