/*******************************************************************************
* Copyright (c) 2014 É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:
* Florian Wininger - Initial API and implementation
* Alexandre Montplaisir - Refactoring, performance tweaks
* Bernd Hufmann - Updated signal handling
* Marc-Andre Laperle - Add time zone preference
* Geneviève Bastien - Moved state system explorer to use the abstract tree viewer
*******************************************************************************/
package fr.inria.linuxtools.tmf.ui.views.statesystem;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import fr.inria.linuxtools.statesystem.core.ITmfStateSystem;
import fr.inria.linuxtools.statesystem.core.exceptions.AttributeNotFoundException;
import fr.inria.linuxtools.statesystem.core.exceptions.StateSystemDisposedException;
import fr.inria.linuxtools.statesystem.core.exceptions.TimeRangeException;
import fr.inria.linuxtools.statesystem.core.interval.ITmfStateInterval;
import fr.inria.linuxtools.statesystem.core.statevalue.ITmfStateValue;
import fr.inria.linuxtools.tmf.core.signal.TmfSignalHandler;
import fr.inria.linuxtools.tmf.core.signal.TmfTimestampFormatUpdateSignal;
import fr.inria.linuxtools.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems;
import fr.inria.linuxtools.tmf.core.timestamp.ITmfTimestamp;
import fr.inria.linuxtools.tmf.core.timestamp.TmfTimestamp;
import fr.inria.linuxtools.tmf.core.trace.ITmfTrace;
import fr.inria.linuxtools.tmf.core.trace.TmfTraceManager;
import fr.inria.linuxtools.tmf.ui.viewers.tree.AbstractTmfTreeViewer;
import fr.inria.linuxtools.tmf.ui.viewers.tree.ITmfTreeColumnDataProvider;
import fr.inria.linuxtools.tmf.ui.viewers.tree.ITmfTreeViewerEntry;
import fr.inria.linuxtools.tmf.ui.viewers.tree.TmfTreeColumnData;
import fr.inria.linuxtools.tmf.ui.viewers.tree.TmfTreeViewerEntry;
/**
* Displays the content of the state systems at the current time
*
* @author Florian Wininger
* @author Alexandre Montplaisir
* @author Geneviève Bastien
* @since 3.0
*/
public class TmfStateSystemViewer extends AbstractTmfTreeViewer {
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private boolean fFilterStatus = false;
private static final int DEFAULT_AUTOEXPAND = 2;
/* Order of columns */
private static final int ATTRIBUTE_NAME_COL = 0;
private static final int QUARK_COL = 1;
private static final int VALUE_COL = 2;
private static final int TYPE_COL = 3;
private static final int START_TIME_COL = 4;
private static final int END_TIME_COL = 5;
private static final int ATTRIBUTE_FULLPATH_COL = 6;
/**
* Base class to provide the labels for the tree viewer. Views extending
* this class typically need to override the getColumnText method if they
* have more than one column to display
*/
protected static class StateSystemTreeLabelProvider extends TreeLabelProvider {
@Override
public String getColumnText(Object element, int columnIndex) {
if (element instanceof StateSystemEntry) {
StateSystemEntry entry = (StateSystemEntry) element;
switch (columnIndex) {
case ATTRIBUTE_NAME_COL:
return entry.getName();
case QUARK_COL:
return String.valueOf(entry.getQuark());
case VALUE_COL:
return entry.getValue();
case TYPE_COL:
return entry.getType();
case START_TIME_COL:
return entry.getStartTime();
case END_TIME_COL:
return entry.getEndTime();
case ATTRIBUTE_FULLPATH_COL:
return entry.getFullPath();
default:
return EMPTY_STRING;
}
}
return super.getColumnText(element, columnIndex);
}
@Override
public Color getBackground(Object element, int columnIndex) {
if (element instanceof StateSystemEntry) {
if (((StateSystemEntry) element).isModified()) {
return Display.getCurrent().getSystemColor(SWT.COLOR_YELLOW);
}
}
return super.getBackground(element, columnIndex);
}
}
/**
* Constructor
*
* @param parent
* The parent containing this viewer
*/
public TmfStateSystemViewer(Composite parent) {
super(parent, false);
this.setLabelProvider(new StateSystemTreeLabelProvider());
getTreeViewer().setAutoExpandLevel(DEFAULT_AUTOEXPAND);
}
@Override
protected ITmfTreeColumnDataProvider getColumnDataProvider() {
return new ITmfTreeColumnDataProvider() {
@Override
public List<TmfTreeColumnData> getColumnData() {
List<TmfTreeColumnData> columns = new ArrayList<>();
TmfTreeColumnData column = new TmfTreeColumnData(Messages.TreeNodeColumnLabel);
columns.add(column);
column.setComparator(new ViewerComparator() {
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
TmfTreeViewerEntry n1 = (TmfTreeViewerEntry) e1;
TmfTreeViewerEntry n2 = (TmfTreeViewerEntry) e2;
return n1.getName().compareTo(n2.getName());
}
});
columns.add(new TmfTreeColumnData(Messages.QuarkColumnLabel));
columns.add(new TmfTreeColumnData(Messages.ValueColumnLabel));
columns.add(new TmfTreeColumnData(Messages.TypeColumnLabel));
columns.add(new TmfTreeColumnData(Messages.StartTimeColumLabel));
columns.add(new TmfTreeColumnData(Messages.EndTimeColumLabel));
columns.add(new TmfTreeColumnData(Messages.AttributePathColumnLabel));
return columns;
}
};
}
// ------------------------------------------------------------------------
// Operations
// ------------------------------------------------------------------------
@Override
protected ITmfTreeViewerEntry updateElements(long start, long end, boolean selection) {
if (getTrace() == null) {
return null;
}
ITmfTreeViewerEntry root = getInput();
if ((!selection) && (root != null)) {
return null;
}
/*
* Build the entries if it is the first time or to show only modified
* values
*/
if (root == null || fFilterStatus) {
root = buildEntries(start);
} else if (root instanceof TmfTreeViewerEntry) {
/*
* Update the values of the elements of the state systems at time
* 'start'
*/
updateEntriesList(((TmfTreeViewerEntry)root).getChildren(), start);
}
return root;
}
private ITmfTreeViewerEntry buildEntries(long timestamp) {
// 'Fake' root node
TmfTreeViewerEntry rootEntry = new TmfTreeViewerEntry(""); //$NON-NLS-1$
List<ITmfTreeViewerEntry> children = rootEntry.getChildren();
for (final ITmfTrace currentTrace : TmfTraceManager.getTraceSet(getTrace())) {
if (currentTrace == null) {
continue;
}
buildEntriesForTrace(currentTrace, timestamp, children);
}
return rootEntry;
}
/*
* Update the values of the entries. It will also create trace and state
* system entries if they do not exist yet.
*/
private void updateEntriesList(List<ITmfTreeViewerEntry> entries, long timestamp) {
for (final ITmfTrace trace : TmfTraceManager.getTraceSet(getTrace())) {
if (trace == null) {
continue;
}
ITmfTreeViewerEntry traceEntry = null;
for (ITmfTreeViewerEntry entry : entries) {
if (entry.getName().equals(trace.getName())) {
traceEntry = entry;
}
}
if (traceEntry == null) {
traceEntry = buildEntriesForTrace(trace, timestamp, entries);
}
/* Find the state system entries for this trace */
Iterable<ITmfAnalysisModuleWithStateSystems> modules = trace.getAnalysisModulesOfClass(ITmfAnalysisModuleWithStateSystems.class);
for (ITmfAnalysisModuleWithStateSystems module : modules) {
module.schedule();
for (ITmfStateSystem ss : module.getStateSystems()) {
if (ss == null) {
continue;
}
ITmfTreeViewerEntry ssEntry = null;
for (ITmfTreeViewerEntry entry : traceEntry.getChildren()) {
if (entry.getName().equals(ss.getSSID())) {
ssEntry = entry;
}
}
if (ssEntry == null) {
/* The state system entry has not been built yet */
buildEntriesForStateSystem(ss, timestamp, (TmfTreeViewerEntry) traceEntry);
} else if (ssEntry.hasChildren()) {
/*
* Typical case at this point, update the data from the
* state system
*/
updateEntriesForStateSystem(ss, timestamp, (TmfTreeViewerEntry) ssEntry);
} else {
/*
* The state system existed but entries were not filled,
* that would occur if for instance the values were out
* of range at the first query.
*/
fillEntriesForStateSystem(ss, timestamp, (TmfTreeViewerEntry) ssEntry);
}
}
}
}
}
@NonNull
private ITmfTreeViewerEntry buildEntriesForTrace(@NonNull ITmfTrace trace, long timestamp, @NonNull List<ITmfTreeViewerEntry> rootEntries) {
TmfTreeViewerEntry traceEntry = new TmfTreeViewerEntry(trace.getName());
rootEntries.add(traceEntry);
Iterable<ITmfAnalysisModuleWithStateSystems> modules = trace.getAnalysisModulesOfClass(ITmfAnalysisModuleWithStateSystems.class);
for (ITmfAnalysisModuleWithStateSystems module : modules) {
/* Just schedule the module, the data will be filled when available */
module.schedule();
for (ITmfStateSystem ss : module.getStateSystems()) {
if (ss == null) {
continue;
}
buildEntriesForStateSystem(ss, timestamp, traceEntry);
}
}
return traceEntry;
}
private void buildEntriesForStateSystem(ITmfStateSystem ss, long timestamp, TmfTreeViewerEntry traceEntry) {
TmfTreeViewerEntry ssEntry = new TmfTreeViewerEntry(ss.getSSID());
traceEntry.addChild(ssEntry);
fillEntriesForStateSystem(ss, timestamp, ssEntry);
}
private void fillEntriesForStateSystem(ITmfStateSystem ss, long timestamp, TmfTreeViewerEntry ssEntry) {
try {
addChildren(ss, ss.queryFullState(timestamp), -1, ssEntry, timestamp);
} catch (StateSystemDisposedException | TimeRangeException e) {
/* Nothing to do */
}
}
/**
* Add children node to an entry. It will create all necessary entries.
*/
private void addChildren(ITmfStateSystem ss, List<ITmfStateInterval> fullState, int rootQuark, TmfTreeViewerEntry root, long timestamp) {
try {
for (int quark : ss.getSubAttributes(rootQuark, false)) {
ITmfStateInterval interval = fullState.get(quark);
StateSystemEntry entry = new StateSystemEntry(ss.getAttributeName(quark), quark, ss.getFullAttributePath(quark),
interval.getStateValue(),
new TmfTimestamp(interval.getStartTime(), ITmfTimestamp.NANOSECOND_SCALE),
new TmfTimestamp(interval.getEndTime(), ITmfTimestamp.NANOSECOND_SCALE));
/* Add this node's children recursively */
addChildren(ss, fullState, quark, entry, timestamp);
/**
* <pre>
* Do not add this entry to root if
* 1- the filter status is ON
* AND
* 2- the entry has no children
* AND
* 3- the start time is not the current timestamp
* </pre>
*/
if (!(fFilterStatus && !entry.hasChildren() && (interval.getStartTime() != timestamp))) {
root.addChild(entry);
}
}
} catch (AttributeNotFoundException e) {
/* Should not happen, we're iterating on known attributes */
throw new RuntimeException();
}
}
private void updateEntriesForStateSystem(ITmfStateSystem ss, long timestamp, TmfTreeViewerEntry ssEntry) {
try {
updateChildren(ss, ss.queryFullState(timestamp), ssEntry);
} catch (StateSystemDisposedException e) {
} catch (TimeRangeException e) {
/* Mark all entries out of range */
markOutOfRange(ssEntry);
}
}
/**
* Update the values of existing entries.
*/
private void updateChildren(ITmfStateSystem ss, List<ITmfStateInterval> fullState, ITmfTreeViewerEntry root) {
for (ITmfTreeViewerEntry entry : root.getChildren()) {
if (entry instanceof StateSystemEntry) {
/*
* FIXME: if new sub attributes were added since the element was
* built, then then will not be added
*/
StateSystemEntry ssEntry = (StateSystemEntry) entry;
ITmfStateInterval interval = fullState.get(ssEntry.getQuark());
if (interval != null) {
ssEntry.update(interval.getStateValue(), new TmfTimestamp(interval.getStartTime(), ITmfTimestamp.NANOSECOND_SCALE),
new TmfTimestamp(interval.getEndTime(), ITmfTimestamp.NANOSECOND_SCALE));
}
/* Update this node's children recursively */
updateChildren(ss, fullState, ssEntry);
}
}
}
/**
* Set the entries as out of range
*/
private void markOutOfRange(ITmfTreeViewerEntry root) {
for (ITmfTreeViewerEntry entry : root.getChildren()) {
if (entry instanceof StateSystemEntry) {
((StateSystemEntry) entry).setOutOfRange();
/* Update this node's children recursively */
markOutOfRange(entry);
}
}
}
/**
* Set the filter status of the viewer. By default, all entries of all state
* system are present, and the values that changed since last refresh are
* shown in yellow. When the filter status is true, only the entries with
* values modified at current time are displayed.
*/
public void changeFilterStatus() {
fFilterStatus = !fFilterStatus;
if (fFilterStatus) {
getTreeViewer().setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
} else {
getTreeViewer().setAutoExpandLevel(DEFAULT_AUTOEXPAND);
clearContent();
}
updateContent(getSelectionBeginTime(), getSelectionEndTime(), true);
}
/**
* Update the display to use the updated timestamp format
*
* @param signal
* the incoming signal
*/
@TmfSignalHandler
public void timestampFormatUpdated(TmfTimestampFormatUpdateSignal signal) {
updateContent(getSelectionBeginTime(), getSelectionEndTime(), true);
}
private class StateSystemEntry extends TmfTreeViewerEntry {
private final int fQuark;
private final String fFullPath;
private @NonNull TmfTimestamp fStart;
private @NonNull TmfTimestamp fEnd;
private ITmfStateValue fValue;
private boolean fModified = false;
private boolean fOutOfRange = false;
public StateSystemEntry(String name, int quark, String fullPath, ITmfStateValue value, @NonNull TmfTimestamp start, @NonNull TmfTimestamp end) {
super(name);
fQuark = quark;
fFullPath = fullPath;
fStart = start;
fEnd = end;
fValue = value;
}
public int getQuark() {
return fQuark;
}
public String getFullPath() {
return fFullPath;
}
public String getStartTime() {
if (fOutOfRange) {
return EMPTY_STRING;
}
return fStart.toString();
}
public String getEndTime() {
if (fOutOfRange) {
return EMPTY_STRING;
}
return fEnd.toString();
}
public String getValue() {
if (fOutOfRange) {
return Messages.OutOfRangeMsg;
}
switch (fValue.getType()) {
case INTEGER:
case LONG:
case DOUBLE:
case STRING:
return fValue.toString();
case NULL:
default:
return EMPTY_STRING;
}
}
public String getType() {
if (fOutOfRange) {
return EMPTY_STRING;
}
switch (fValue.getType()) {
case INTEGER:
return Messages.TypeInteger;
case LONG:
return Messages.TypeLong;
case DOUBLE:
return Messages.TypeDouble;
case STRING:
return Messages.TypeString;
case NULL:
default:
return EMPTY_STRING;
}
}
public boolean isModified() {
return fModified;
}
public void update(ITmfStateValue value, @NonNull TmfTimestamp start, @NonNull TmfTimestamp end) {
fModified = false;
fOutOfRange = false;
if (!start.equals(fStart)) {
fModified = true;
fStart = start;
fEnd = end;
fValue = value;
}
}
public void setOutOfRange() {
fModified = false;
fOutOfRange = true;
}
}
}