/*******************************************************************************
* Copyright (c) 2014, 2016 École Polytechnique de Montréal
*
* 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:
* Geneviève Bastien - Initial API and implementation
* Alexandre Montplaisir - Initial API and implementation
* Patrick Tasse - Add message to exceptions
*******************************************************************************/
package org.eclipse.tracecompass.statesystem.core;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
/**
* Provide utility methods for the state system
*
* @author Geneviève Bastien
* @author Loïc Prieur-Drevon
*/
@NonNullByDefault
public final class StateSystemUtils {
private StateSystemUtils() {
}
/**
* Convenience method to query attribute stacks (created with
* pushAttribute()/popAttribute()). This will return the interval that is
* currently at the top of the stack, or 'null' if that stack is currently
* empty. It works similarly to querySingleState().
*
* To retrieve the other values in a stack, you can query the sub-attributes
* manually.
*
* @param ss
* The state system to query
* @param t
* The timestamp of the query
* @param stackAttributeQuark
* The top-level stack-attribute (that was the target of
* pushAttribute() at creation time)
* @return The interval that was at the top of the stack, or 'null' if the
* stack was empty.
* @throws StateValueTypeException
* If the target attribute is not a valid stack attribute (if it
* has a string value for example)
* @throws AttributeNotFoundException
* If the attribute was simply not found
* @throws TimeRangeException
* If the given timestamp is invalid
* @throws StateSystemDisposedException
* If the query is sent after the state system has been disposed
*/
public static @Nullable ITmfStateInterval querySingleStackTop(ITmfStateSystem ss,
long t, int stackAttributeQuark)
throws AttributeNotFoundException, StateSystemDisposedException {
ITmfStateValue curStackStateValue = ss.querySingleState(t, stackAttributeQuark).getStateValue();
if (curStackStateValue.isNull()) {
/* There is nothing stored in this stack at this moment */
return null;
}
int curStackDepth = curStackStateValue.unboxInt();
if (curStackDepth <= 0) {
/*
* This attribute is an integer attribute, but it doesn't seem like
* it's used as a stack-attribute...
*/
throw new StateValueTypeException(ss.getSSID() + " Quark:" + stackAttributeQuark + ", Stack depth:" + curStackDepth); //$NON-NLS-1$//$NON-NLS-2$
}
int subAttribQuark = ss.getQuarkRelative(stackAttributeQuark, String.valueOf(curStackDepth));
return ss.querySingleState(t, subAttribQuark);
}
/**
* Return a list of state intervals, containing the "history" of a given
* attribute between timestamps t1 and t2. The list will be ordered by
* ascending time.
*
* Note that contrary to queryFullState(), the returned list here is in the
* "direction" of time (and not in the direction of attributes, as is the
* case with queryFullState()).
*
* @param ss
* The state system to query
* @param attributeQuark
* Which attribute this query is interested in
* @param t1
* Start time of the range query
* @param t2
* Target end time of the query. If t2 is greater than the end of
* the trace, we will return what we have up to the end of the
* history.
* @return The List of state intervals that happened between t1 and t2
* @throws TimeRangeException
* If t1 is invalid, or if t2 <= t1
* @throws AttributeNotFoundException
* If the requested quark does not exist in the model.
* @throws StateSystemDisposedException
* If the query is sent after the state system has been disposed
*/
public static List<ITmfStateInterval> queryHistoryRange(ITmfStateSystem ss,
int attributeQuark, long t1, long t2)
throws AttributeNotFoundException, StateSystemDisposedException {
List<ITmfStateInterval> intervals;
ITmfStateInterval currentInterval;
long ts, tEnd;
/* Make sure the time range makes sense */
if (t2 < t1) {
throw new TimeRangeException(ss.getSSID() + " Start:" + t1 + ", End:" + t2); //$NON-NLS-1$ //$NON-NLS-2$
}
/* Set the actual, valid end time of the range query */
if (t2 > ss.getCurrentEndTime()) {
tEnd = ss.getCurrentEndTime();
} else {
tEnd = t2;
}
/* Get the initial state at time T1 */
intervals = new ArrayList<>();
currentInterval = ss.querySingleState(t1, attributeQuark);
intervals.add(currentInterval);
/* Get the following state changes */
ts = currentInterval.getEndTime();
while (ts != -1 && ts < tEnd) {
ts++; /* To "jump over" to the next state in the history */
currentInterval = ss.querySingleState(ts, attributeQuark);
intervals.add(currentInterval);
ts = currentInterval.getEndTime();
}
return intervals;
}
/**
* Return the state history of a given attribute, but with at most one
* update per "resolution". This can be useful for populating views (where
* it's useless to have more than one query per pixel, for example). A
* progress monitor can be used to cancel the query before completion.
*
* @param ss
* The state system to query
* @param attributeQuark
* Which attribute this query is interested in
* @param t1
* Start time of the range query
* @param t2
* Target end time of the query. If t2 is greater than the end of
* the trace, we will return what we have up to the end of the
* history.
* @param resolution
* The "step" of this query
* @param monitor
* A progress monitor. If the monitor is canceled during a query,
* we will return what has been found up to that point. You can
* use "null" if you do not want to use one.
* @return The List of states that happened between t1 and t2
* @throws TimeRangeException
* If t1 is invalid, if t2 <= t1, or if the resolution isn't
* greater than zero.
* @throws AttributeNotFoundException
* If the attribute doesn't exist
* @throws StateSystemDisposedException
* If the query is sent after the state system has been disposed
*/
public static List<ITmfStateInterval> queryHistoryRange(ITmfStateSystem ss,
int attributeQuark, long t1, long t2, long resolution,
@Nullable IProgressMonitor monitor)
throws AttributeNotFoundException, StateSystemDisposedException {
List<ITmfStateInterval> intervals = new LinkedList<>();
ITmfStateInterval currentInterval = null;
long ts, tEnd;
/* Make sure the time range makes sense */
if (t2 < t1 || resolution <= 0) {
throw new TimeRangeException(ss.getSSID() + " Start:" + t1 + ", End:" + t2 + ", Resolution:" + resolution); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
/* Set the actual, valid end time of the range query */
if (t2 > ss.getCurrentEndTime()) {
tEnd = ss.getCurrentEndTime();
} else {
tEnd = t2;
}
IProgressMonitor mon = monitor;
if (mon == null) {
mon = new NullProgressMonitor();
}
/*
* Iterate over the "resolution points". We skip unneeded queries in the
* case the current interval is longer than the resolution.
*/
for (ts = t1; ts <= tEnd; ts += ((currentInterval.getEndTime() - ts) / resolution + 1) * resolution) {
if (mon.isCanceled()) {
return intervals;
}
currentInterval = ss.querySingleState(ts, attributeQuark);
intervals.add(currentInterval);
}
/* Add the interval at t2, if it wasn't included already. */
if (currentInterval != null && currentInterval.getEndTime() < tEnd) {
currentInterval = ss.querySingleState(tEnd, attributeQuark);
intervals.add(currentInterval);
}
return intervals;
}
/**
* Queries intervals in the state system for a given attribute, starting at
* time t1, until we obtain a non-null value.
*
* @param ss
* The state system on which to query intervals
* @param attributeQuark
* The attribute quark to query
* @param t1
* Start time of the query
* @param t2
* Time limit of the query. Use {@link Long#MAX_VALUE} for no
* limit.
* @return The first interval from t1 for which the value is not a null
* value, or <code>null</code> if no interval was found once we
* reach either t2 or the end time of the state system.
*/
public static @Nullable ITmfStateInterval queryUntilNonNullValue(ITmfStateSystem ss,
int attributeQuark, long t1, long t2) {
long current = t1;
/* Make sure the range is ok */
if (t1 < ss.getStartTime()) {
current = ss.getStartTime();
}
long end = t2;
if (end < ss.getCurrentEndTime()) {
end = ss.getCurrentEndTime();
}
/* Make sure the time range makes sense */
if (end < current) {
return null;
}
try {
while (current < t2) {
ITmfStateInterval currentInterval = ss.querySingleState(current, attributeQuark);
ITmfStateValue value = currentInterval.getStateValue();
if (!value.isNull()) {
return currentInterval;
}
current = currentInterval.getEndTime() + 1;
}
} catch (StateSystemDisposedException | TimeRangeException e) {
/* Nothing to do */
}
return null;
}
/**
* Iterator class to allow 2-way iteration over intervals of a given
* attribute. Not thread-safe!
*
* @since 2.2
*/
public static class QuarkIterator implements Iterator<ITmfStateInterval> {
private final ITmfStateSystem fSS;
private final int fQuark;
private final long fInitialTime;
private @Nullable ITmfStateInterval fCurrent;
/**
* Constructor
*
* @param ss
* The state system on which to query intervals
* @param quark
* The key to the attribute to iterate over
* @param initialTime
* The timestamp that the first returned interval will
* intersect. This timestamp can be smaller than the
* StateSystem's start time, in which case, iteration will
* start at the StateSystem's start, on bigger than the
* StateSystem's current end time, in which case iteration
* will start at the StateSystem's current end time.
* @throws TimeRangeException
* If end < start.
* @since 2.1
*/
public QuarkIterator(ITmfStateSystem ss, int quark, long initialTime) {
fSS = ss;
fQuark = quark;
fInitialTime = initialTime;
}
private long getNextQueryTime() {
if (fCurrent != null) {
return (fCurrent.getEndTime() + 1);
}
/* Iteration has not started yet */
return Long.max(fInitialTime, fSS.getStartTime());
}
private long getPreviousQueryTime() {
if (fCurrent != null) {
return fCurrent.getStartTime() - 1;
}
/* Iteration has not started yet */
return Long.min(fInitialTime, fSS.getCurrentEndTime());
}
@Override
public boolean hasNext() {
/*
* Ensure that the next query time falls within state system and
* query time range. By definition getNextQueryTime() is larger than
* the state system's start time.
*/
return (getNextQueryTime() <= fSS.getCurrentEndTime());
}
@Override
public ITmfStateInterval next() {
if (hasNext()) {
try {
fCurrent = fSS.querySingleState(getNextQueryTime(), fQuark);
return fCurrent;
} catch (StateSystemDisposedException e) {
/* GOTO throw NoSuchElementException. */
}
}
throw new NoSuchElementException();
}
/**
* Returns true if the iteration has more previous elements. (In other
* words, returns true if previous() would return an element rather than
* throwing an exception.)
*
* @return true if the iteration has more previous elements
*/
public boolean hasPrevious() {
/*
* Ensure that the next query time falls within state system and
* query time range. By definition getPreviousQueryTime() is smaller
* than the state system's end time.
*/
return (getPreviousQueryTime() >= fSS.getStartTime());
}
/**
* Returns the previous element in the iteration.
*
* @return the previous element in the iteration
*/
public ITmfStateInterval previous() {
if (hasPrevious()) {
try {
fCurrent = fSS.querySingleState(getPreviousQueryTime(), fQuark);
return fCurrent;
} catch (StateSystemDisposedException e) {
/* GOTO throw NoSuchElementException. */
}
}
throw new NoSuchElementException();
}
}
}