/*******************************************************************************
* Copyright (c) 2011, 2014 Ericsson, Ecole Polytechnique de Montreal and others
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors: Matthew Khouzam - Initial API and implementation
* Contributors: Simon Marchi - Initial API and implementation
* Contributors: Etienne Bergeron <etienne.bergeron@gmail.com>
* Contributors: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
*******************************************************************************/
package org.eclipse.tracecompass.internal.ctf.core.trace;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.TreeSet;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.tracecompass.ctf.core.trace.ICTFPacketDescriptor;
import org.eclipse.tracecompass.internal.ctf.core.Activator;
/**
* <b><u>StreamInputPacketIndex</u></b>
* <p>
* This is a data structure containing entries, you may append to this and read
* it. It is not thread safe.
*/
public class StreamInputPacketIndex {
// ------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------
/**
* Entries of the index. They are sorted by increasing begin timestamp.
* index builder.
*/
private final List<ICTFPacketDescriptor> fEntries = new ArrayList<>();
// ------------------------------------------------------------------------
// Operations
// ------------------------------------------------------------------------
/**
* Returns the number of elements in this data structure. If this data
* structure contains more than {@code Integer.MAX_VALUE} elements, returns
* {@code Integer.MAX_VALUE}.
*
* @return the number of elements in this data structure
*/
public int size() {
return fEntries.size();
}
/**
* Returns {@code true} if this data structure contains no elements.
*
* @return {@code true} if this data structure contains no elements
*/
public boolean isEmpty() {
return fEntries.isEmpty();
}
/**
* Adds a collection of entries to the index, the entries must be sorted.
*
* @param preParsedIndex
* the pre-parsed index file
*/
public void appendAll(Collection<ICTFPacketDescriptor> preParsedIndex) {
for (ICTFPacketDescriptor sipie : preParsedIndex) {
append(checkNotNull(sipie));
}
}
/**
* Appends the specified element to the end of this data structure
*
* @param entry
* element to be appended to this index, cannot be null
* @return {@code true} (as specified by {@link Collection#add})
*/
public synchronized boolean append(@NonNull ICTFPacketDescriptor entry) {
ICTFPacketDescriptor entryToAdd = entry;
/* Validate consistent entry. */
if (entryToAdd.getTimestampBegin() > entryToAdd.getTimestampEnd()) {
Activator.log(IStatus.WARNING, "Packet at offset " + entryToAdd.getOffsetBytes() + //$NON-NLS-1$
" begin timestamp is after end timestamp"); //$NON-NLS-1$
entryToAdd = new StreamInputPacketIndexEntry(entryToAdd, Long.MAX_VALUE);
}
/*
* Validate entries are inserted in monotonic increasing timestamp
* order.
*/
if (!fEntries.isEmpty() && (entryToAdd.getTimestampBegin() < lastElement().getTimestampBegin())) {
return false;
}
fEntries.add(entryToAdd);
return true;
}
/**
* Returns the first packet that could include the timestamp, that is the
* last packet with a begin timestamp smaller than the given timestamp.
*
* @param timestamp
* The timestamp to look for.
* @return The index of the desired packet
*/
public int search(final long timestamp) {
/*
* Search using binary search.
*
* As the entries in fEntries are IndexEntries, the key to search for
* needs to be one too. We are looking for a timestamp though, so we use
* the dataOffset which is a long and use it as a timestamp holder.
*/
int index = Collections.binarySearch(fEntries, new StreamInputPacketIndexEntry(timestamp, 0), new FindTimestamp());
if (index < 0) {
index = -index - 1;
}
return index;
}
/**
* Get the last element of the index
*
* @return the last element in the index
*/
public ICTFPacketDescriptor lastElement() {
return fEntries.get(fEntries.size() - 1);
}
/**
* Returns the element at the specified position in this data structure.
*
* @param index
* index of the element to return
* @return the element at the specified position in this data structure
* @throws IndexOutOfBoundsException
* if the index is out of range (
* {@code index < 0 || index >= size()})
*/
public ICTFPacketDescriptor getElement(int index) {
return fEntries.get(index);
}
/**
* Returns the index of the first occurrence of the specified element in
* this data structure, or -1 if this data structure does not contain the
* element. More formally, returns the lowest index {@code i} such that, for
* an entry {@code o}, {@code (o==null ? get(i)==null : o.equals(get(i)))},
* or {@code -1} if there is no such index. This will work in log(n) time
* since the data structure contains elements in a non-repeating increasing
* manner.
*
* @param element
* element to search for
* @return the index of the first occurrence of the specified element in
* this data structure, or -1 if this data structure does not
* contain the element
* @throws ClassCastException
* if the type of the specified element is incompatible with
* this data structure (
* <a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException
* if the specified element is null and this data structure does
* not permit null elements (
* <a href="Collection.html#optional-restrictions">optional</a>)
*/
public int indexOf(ICTFPacketDescriptor element) {
int indexOf = -1;
if (element != null) {
indexOf = Collections.binarySearch(fEntries, element, new MonotonicComparator());
}
return (indexOf < 0) ? -1 : indexOf;
}
/**
* Ordering comparator for entering entries into a data structure sorted by
* timestamp.
*/
private static class MonotonicComparator implements Comparator<ICTFPacketDescriptor>, Serializable {
/**
* For {@link Serializable}, that way if we migrate to a {@link TreeSet}
* the comparator is serializable too.
*/
private static final long serialVersionUID = -5693064068367242076L;
@Override
public int compare(ICTFPacketDescriptor left, ICTFPacketDescriptor right) {
if (left.getTimestampBegin() > right.getTimestampBegin()) {
return 1;
}
if (left.getTimestampBegin() < right.getTimestampBegin()) {
return -1;
}
if (left.getTimestampEnd() > right.getTimestampEnd()) {
return 1;
}
if (left.getTimestampEnd() < right.getTimestampEnd()) {
return -1;
}
return 0;
}
}
/**
* Used for search, assumes that the second argument in the comparison is
* always the key
*/
private static class FindTimestamp implements Comparator<ICTFPacketDescriptor>, Serializable {
/**
* UID
*/
private static final long serialVersionUID = 7235997205945550341L;
@Override
public int compare(ICTFPacketDescriptor value, ICTFPacketDescriptor key) {
/*
* It is assumed that the second packet descriptor is the key, a
* wrapped timestamp in a PacketDescriptor. So we need to extract
* the timestamp. Then we have 3 choices, the if the timestamp is in
* the interval, we return 0 or found. If the timestamp is before or
* after, we just need to compare it to a value in the segment (like
* start) to know if it is greater or lesser to the current packet.
*/
long ts = key.getOffsetBits();
if (value.includes(ts)) {
return 0;
}
return Long.compare(value.getTimestampBegin(), ts);
}
}
}