blob: df4816eb85a39224a7273e21a429d5eddd9bdc4f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 Ericsson
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.internal.ctf.core.utils;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
/**
* <p>
* Sparse list, a list optimized for when most of the data is <code>null</code>.
* Nulls will increment the size of the SparseList but they are not stored
* internally.
* </p>
* <p>
* Note: this iterates in the list order.
* </p>
* This implementation does not support:
* <ul>
* <li>{@link #add(int, Object)}</li>
* <li>{@link #addAll(int, Collection)}</li>
* <li>{@link #remove(int)}</li>
* <li>{@link #remove(Object)}</li>
* <li>{@link #removeAll(Collection)}</li>
* <li>{@link #subList(int, int)}</li>
* </ul>
* </p>
* <p>
* An efficient shift operation for bulk moving elements in a list would be
* needed in order to implement these features.
* </p>
* TODO: Keep an eye out for a better datastructure... SparseList is intended as
* a stop-gap fix. It is fine, but if it can be replaced by an externally
* maintained datastructure, that would be better.
*
* @author Matthew Khouzam
* @param <E>
* the element type
*/
public class SparseList<E> implements List<E> {
/**
* A backing map used to store the non-null elements
*/
private final Map<Integer, @NonNull E> fInnerElements = new HashMap<>();
/**
* The list size: map size + number of nulls
*/
private int fSize = 0;
/**
* Copy constructor
*
* @param events
* list of events
*/
public SparseList(List<E> events) {
ensureSize(events.size());
for (int i = 0; i < events.size(); i++) {
E element = events.get(i);
if (element != null) {
set(i, element);
}
}
}
/**
* default constructor
*/
public SparseList() {
// Do nothing
}
@Override
public int size() {
return fSize;
}
@Override
public boolean isEmpty() {
return fSize == 0;
}
@Override
public boolean contains(Object o) {
return o == null ? size() > fInnerElements.size() : fInnerElements.containsValue(o);
}
@Override
public Iterator<E> iterator() {
return new GenericReadOnlyListIterator<>(this, 0);
}
/**
* Break in contract with {@link List#toArray()} implementation. This
* returns an list-ordered array of size N where N is the number of non-null
* elements. This is a design concession to avoid out of memory errors with
* the sparse list, since this list should only be used when memory
* footprint is important.
*
* The returned array will be "safe" in that no references to it are
* maintained by this list. (In other words, this method must allocate a new
* array even if this list is backed by an array). The caller is thus free
* to modify the returned array.
*
* @return an array containing all of the non-null elements in this list in
* proper sequence
*/
@Override
public Object[] toArray() {
int size = fInnerElements.size();
Object[] retVal = new Object[size];
Iterator<E> iterator = iterator();
for (int i = 0; i < size; i++) {
Object next = null;
while (iterator.hasNext() && next == null) {
next = iterator.next();
}
retVal[i] = next;
}
return retVal;
}
/**
* Break in contract with {@link List#toArray(Object[])} implementation.
*
* Returns an array containing all of the <strong>non-null</strong> elements
* in this list in proper sequence (from first to last element); the runtime
* type of the returned array is that of the specified array. If the list
* fits in the specified array, it is returned therein. Otherwise, only the
* first n elements of the list are returned.
*
* This is a design concession to avoid out of memory errors with the sparse
* list, since this list should only be used when memory footprint is
* important.
*
* @param newArray
* the array to return
* @return newArray, filled with values.
*/
@SuppressWarnings("unchecked")
@Override
public <T> T[] toArray(T[] newArray) {
Class<?> componentType = newArray.getClass().getComponentType();
T[] returnArray = newArray;
int size = fInnerElements.size();
if (returnArray.length < size) {
returnArray = (T[]) Array.newInstance(componentType, size);
}
for (int i = size; i < returnArray.length; i++) {
returnArray[i] = null;
}
Iterator<E> iterator = iterator();
for (int i = 0; i < size; i++) {
@Nullable E next = null;
while (iterator.hasNext() && next == null) {
next = iterator.next();
}
returnArray[i] = (T) next;
}
return returnArray;
}
@Override
public boolean add(E e) {
if (e != null) {
fInnerElements.put(fSize, e);
}
fSize++;
return true;
}
@Override
public boolean containsAll(Collection<?> c) {
Collection<?> nonNullCollection = c;
if (nonNullCollection.contains(null)) {
if (!contains(null)) {
return false;
}
nonNullCollection = c.stream().filter(Objects::nonNull).collect(Collectors.toList());
}
return fInnerElements.values().containsAll(nonNullCollection);
}
@Override
public boolean addAll(Collection<? extends E> c) {
int key = fSize;
fSize += c.size();
for (E event : c) {
set(key, event);
key++;
}
return true;
}
@Override
public E get(int index) {
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException("Tried to access index " + index + " Sparse list size " + fSize); //$NON-NLS-1$ //$NON-NLS-2$
}
return fInnerElements.get(index);
}
@Override
public E set(int index, E element) {
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException("Tried to add to index " + index + " Sparse list size " + fSize); //$NON-NLS-1$ //$NON-NLS-2$
}
if (element != null) {
return fInnerElements.put(index, element);
}
return fInnerElements.remove(index);
}
@Override
public int indexOf(Object o) {
if (o == null && contains(null)) {
for (int i = 0; i < size(); i++) {
if (!fInnerElements.containsKey(i)) {
return i;
}
}
}
int first = Integer.MAX_VALUE;
for (Entry<Integer, E> entry : fInnerElements.entrySet()) {
if (Objects.equals(entry.getValue(), o)) {
first = Math.min(entry.getKey(), first);
}
}
return first == Integer.MAX_VALUE ? -1 : first;
}
@Override
public int lastIndexOf(Object o) {
int last = -1;
if (o == null && contains(null)) {
for (int i = size() - 1; i >= 0; i--) {
if (!fInnerElements.containsKey(i)) {
return i;
}
}
}
for (Entry<Integer, E> entry : fInnerElements.entrySet()) {
if (Objects.equals(entry.getValue(), o)) {
last = Math.max(last, entry.getKey());
}
}
return last;
}
@Override
public void clear() {
fInnerElements.clear();
fSize = 0;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(iterator(), fSize, Spliterator.ORDERED);
}
@Override
public ListIterator<E> listIterator() {
return new GenericReadOnlyListIterator<>(this, 0);
}
@Override
public ListIterator<E> listIterator(int index) {
return new GenericReadOnlyListIterator<>(this, index);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < size(); i++) {
E element = get(i);
if (element != null) {
sb.append(i).append(':').append(String.valueOf(element));
if (i < size() - 1) {
sb.append(',').append(' ');
}
}
}
sb.append(']');
return sb.toString();
}
/**
* resize the list
*
* @param requestedSize
* the new size
*/
public void ensureSize(int requestedSize) {
fSize = Math.max(fSize, requestedSize);
}
@Override
public void add(int index, E element) {
throw new UnsupportedOperationException("No add(int, E) in " + this.getClass().getName()); //$NON-NLS-1$
}
@Override
public E remove(int index) {
throw new UnsupportedOperationException("No remove(int) in " + this.getClass().getName()); //$NON-NLS-1$
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException("No remove(Object) in " + this.getClass().getName()); //$NON-NLS-1$
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
throw new UnsupportedOperationException("No addAll(int, Collection<? extends E>) in " + this.getClass().getName()); //$NON-NLS-1$
}
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException("No removeAll(Collection<?>) in " + this.getClass().getName()); //$NON-NLS-1$
}
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException("No retainAll(Collection<?> in " + this.getClass().getName()); //$NON-NLS-1$
}
@Override
public @NonNull List<E> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException("No subList(int, int) in " + this.getClass().getName()); //$NON-NLS-1$
}
}