/******************************************************************************* * Copyright 2012-2013 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.util.misc; import static java.util.Objects.*; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import com.google.common.collect.Iterators; import cern.colt.map.OpenLongObjectHashMap; import net.jcip.annotations.NotThreadSafe; /* * MapList is a collection that stores data in both a HashMap and * an ArrayList. This preserves order and also allows objects to be indexed by Id. */ @NotThreadSafe public class MapList<T extends IGetId> implements IMapList<T> { private @Nullable Iterable<T> _iterable; private @Nullable OpenLongObjectHashMap _hashMap; private @Nullable ArrayList<T> _arrayList; /*--------------- * Construction */ public MapList() { this(16); } public MapList(int initialCapacity) { _hashMap = new OpenLongObjectHashMap(initialCapacity); _arrayList = new ArrayList<T>(initialCapacity); } /** * Construct lazily from an iterable. * <p> * The contents will be read from the {@code iterable} the first time a method other * than {@link #iterator} is invoked. This is to avoid copying many objects in the common * situation in which the list is only used to iterate over its contents. * <p> * @param iterable * @since 0.08 */ public MapList(Iterable<T> iterable) { _iterable = iterable; } /*-------------------- * Collection methods */ /** * Adds element to the collection. The same element may be added multiple times and will * appear multiple times when using list-related operations such as {@link #iterator()} * and {@link #getByIndex(int)}. * * @return true */ @Override @NonNullByDefault(false) public boolean add(T node) { hashMap().put(node.getGlobalId(), node); arrayList().add(node); return true; } @Override @NonNullByDefault(false) public boolean addAll(Collection<? extends T> collection) { if (collection == this) { return false; } ensureCapacity(size() + collection.size()); boolean changed = false; for (T t : collection) { if (add(t)) changed = true; } return changed; } @Override public void clear() { _iterable = null; final ArrayList<T> arrayList = _arrayList; if (arrayList != null) { arrayList.clear(); } final OpenLongObjectHashMap hashMap = _hashMap; if (hashMap != null) { hashMap.clear(); } } @Override public boolean contains(@Nullable Object arg0) { return (arg0 instanceof IGetId) ? contains((IGetId)arg0) : false ; } @Override public boolean containsAll(@Nullable Collection<?> objects) { for (Object object : requireNonNull(objects)) { if (!contains(object)) { return false; } } return true; } /** * Visits entries in index order. {@link Iterator#remove()} is not supported. * <p> * If this collection was constructed using the {@link #MapList(Iterable)} constructor * and no other method has been called, this will simply use the iterable to create * an iterator. */ @Override public Iterator<T> iterator() { Iterable<T> iterable = _iterable; if (iterable == null) { iterable = requireNonNull(_arrayList); } return Iterators.unmodifiableIterator(iterable.iterator()); } @Override public boolean isEmpty() { return arrayList().isEmpty(); } /** * Removes all instances of this object from the collection. * @return false if no instances were found. * @see #removeByIndex(int) */ @Override public boolean remove(@Nullable Object obj) { boolean removed = false; if (obj instanceof IGetId) { final IGetId node = (IGetId)obj; OpenLongObjectHashMap hashMap = hashMap(); removed = hashMap.removeKey(node.getGlobalId()); if (removed) { final ArrayList<T> arrayList = arrayList(); int nLeft = arrayList.size() - hashMap.size(); if (nLeft <= 1) { // There can only be one instance, so a simple remove call is sufficient. arrayList.remove(node); } else { Iterator<T> arrayIter = arrayList.iterator(); while (arrayIter.hasNext()) { if (arrayIter.next() == node) { arrayIter.remove(); } } } } } return removed; } @Override public boolean removeAll(@Nullable Collection<?> elements) { boolean changed = false; for (Object o : requireNonNull(elements)) { if(remove(o)) changed = true; } return changed; } @Override public boolean retainAll(@Nullable Collection<?> keep) { requireNonNull(keep); boolean changed = false; for (int i = size(); --i >= 0;) { T value = getByIndex(i); if (!keep.contains(value)) { remove(value); changed = true; } } return changed; } @Override public int size() { return arrayList().size(); } @Override public Object[] toArray() { return arrayList().toArray(); } @Override @NonNullByDefault(false) public <T2> T2[] toArray(T2[] array) { return arrayList().toArray(array); } /*------------------ * IMapList methods */ @Override public void addAll(@Nullable T[] nodes) { if (nodes != null) { ensureCapacity(size() + nodes.length); for (T n : nodes) add(n); } } @Override public boolean contains(IGetId node) { return hashMap().containsKey(node.getGlobalId()); } @Override public void ensureCapacity(int minCapacity) { hashMap().ensureCapacity(minCapacity); arrayList().ensureCapacity(minCapacity); } @Override public @Nullable T getByKey(long id) { @SuppressWarnings("unchecked") T value = (T) hashMap().get(id); return value; } /** * @return element at given {@code index} which depends on the order in which the element * was added to the list. */ @NonNull // FIXME - workaround for Eclipse JDT bug (467610?) @Override public T getByIndex(int index) { return arrayList().get(index); } /** * Removes and returns element at given {@code index}. * <p> * This only removes the specified instance. If the element may have been added more * than once, and you want to remove all instances instead use {@link #remove(Object)}. */ @Override public @Nullable T removeByIndex(int index) { final ArrayList<T> arrayList = arrayList(); final T elt = arrayList.remove(index); final OpenLongObjectHashMap hashMap = hashMap(); if (hashMap.size() < arrayList.size() || !arrayList.contains(elt)) { // If map is smaller than the array, then there can't have been more than // one instance of each element so we can skip the contains test. hashMap.removeKey(elt.getGlobalId()); } return elt; } @Override public List<T> values() { return arrayList(); } /*----------------- * Private methods */ private ArrayList<T> arrayList() { if (_arrayList == null) { load(); } return requireNonNull(_arrayList); } private OpenLongObjectHashMap hashMap() { if (_hashMap == null) { load(); } return requireNonNull(_hashMap); } private void load() { final ArrayList<T> arrayList = _arrayList = new ArrayList<>(); final OpenLongObjectHashMap hashMap = _hashMap = new OpenLongObjectHashMap(); Iterable<T> iterable = _iterable; if (iterable != null) { for (T node : iterable) { arrayList.add(node); hashMap.put(node.getGlobalId(), node); } _iterable = null; } } }