/*******************************************************************************
* Copyright (c) 2014, 2016 École Polytechnique de Montréal 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:
* 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
* Patrick Tasse - Refactoring
*******************************************************************************/
package org.eclipse.tracecompass.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 org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule;
import org.eclipse.tracecompass.tmf.core.signal.TmfSignalHandler;
import org.eclipse.tracecompass.tmf.core.signal.TmfTimestampFormatUpdateSignal;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceContext;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.AbstractTmfTreeViewer;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.ITmfTreeColumnDataProvider;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.ITmfTreeViewerEntry;
import org.eclipse.tracecompass.tmf.ui.viewers.tree.TmfTreeColumnData;
import org.eclipse.tracecompass.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
*/
public class TmfStateSystemViewer extends AbstractTmfTreeViewer {
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private static final int DEFAULT_AUTOEXPAND = 2;
private boolean fFilterStatus = false;
private long fSelection = 0;
/* 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 StateEntry) {
StateEntry entry = (StateEntry) 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 StateEntry) {
if (((StateEntry) 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 (selection) {
fSelection = start;
} else {
TmfTraceContext ctx = TmfTraceManager.getInstance().getCurrentTraceContext();
fSelection = ctx.getSelectionRange().getStartTime().toNanos();
}
if (getTrace() == null) {
return null;
}
ITmfTreeViewerEntry root = getInput();
if (root == null) {
root = createRoot();
} else if (fFilterStatus) {
clearStateSystemEntries(root);
}
/*
* Update the values of the elements of the state systems at the
* selection start time
*/
boolean changed = updateStateSystemEntries(root, fSelection);
return selection || changed ? root : null;
}
private ITmfTreeViewerEntry createRoot() {
// 'Fake' root node
TmfTreeViewerEntry rootEntry = new TmfTreeViewerEntry("root"); //$NON-NLS-1$
for (final ITmfTrace trace : TmfTraceManager.getTraceSetWithExperiment(getTrace())) {
rootEntry.addChild(createTraceEntry(trace));
}
return rootEntry;
}
private static TmfTreeViewerEntry createTraceEntry(ITmfTrace trace) {
TmfTreeViewerEntry traceEntry = new TmfTreeViewerEntry(trace.getName());
Iterable<IAnalysisModule> modules = trace.getAnalysisModules();
for (IAnalysisModule module : modules) {
if (module instanceof ITmfAnalysisModuleWithStateSystems) {
ITmfAnalysisModuleWithStateSystems moduleWithStateSystem = (ITmfAnalysisModuleWithStateSystems) module;
// Add the module as an entry to the trace
TmfTreeViewerEntry moduleEntry = new ModuleEntry(moduleWithStateSystem);
traceEntry.addChild(moduleEntry);
// Add the state system as children of the module, they may not
// be initialized yet, the list will be empty in that case
for (ITmfStateSystem ss : moduleWithStateSystem.getStateSystems()) {
moduleEntry.addChild(new StateSystemEntry(ss));
}
}
}
return traceEntry;
}
private static void clearStateSystemEntries(ITmfTreeViewerEntry root) {
for (ITmfTreeViewerEntry traceEntry : root.getChildren()) {
for (ITmfTreeViewerEntry ssEntry : traceEntry.getChildren()) {
ssEntry.getChildren().clear();
}
}
}
private boolean updateStateSystemEntries(ITmfTreeViewerEntry root, long timestamp) {
boolean changed = false;
for (ITmfTreeViewerEntry traceEntry : root.getChildren()) {
for (ITmfTreeViewerEntry moduleEntry : traceEntry.getChildren()) {
// If there are no children, see if new state systems are
// available now
if (moduleEntry.getChildren().isEmpty()) {
for (ITmfStateSystem ss : ((ModuleEntry) moduleEntry).getModule().getStateSystems()) {
((ModuleEntry) moduleEntry).addChild(new StateSystemEntry(ss));
}
}
for (ITmfTreeViewerEntry ssEntry : moduleEntry.getChildren()) {
StateSystemEntry stateSystemEntry = (StateSystemEntry) ssEntry;
ITmfStateSystem ss = stateSystemEntry.getSS();
try {
List<ITmfStateInterval> fullState = ss.queryFullState(timestamp);
changed |= updateStateEntries(ss, fullState, stateSystemEntry, -1, timestamp);
} catch (TimeRangeException e) {
markOutOfRange(stateSystemEntry);
changed = true;
} catch (StateSystemDisposedException e) {
/* Ignored */
}
}
}
}
return changed;
}
private boolean updateStateEntries(ITmfStateSystem ss, List<ITmfStateInterval> fullState, TmfTreeViewerEntry parent, int parentQuark, long timestamp) {
boolean changed = false;
for (int quark : ss.getSubAttributes(parentQuark, false)) {
if (quark >= fullState.size()) {
// attribute was created after the full state query
continue;
}
ITmfStateInterval interval = fullState.get(quark);
StateEntry stateEntry = findStateEntry(parent, quark);
if (stateEntry == null) {
boolean modified = fFilterStatus ?
interval.getStartTime() == timestamp :
!interval.getStateValue().isNull();
stateEntry = new StateEntry(ss.getAttributeName(quark), quark, ss.getFullAttributePath(quark),
interval.getStateValue(),
TmfTimestamp.fromNanos(interval.getStartTime()),
TmfTimestamp.fromNanos(interval.getEndTime()),
modified);
// update children first to know if parent is really needed
updateStateEntries(ss, fullState, stateEntry, quark, timestamp);
/*
* Add this entry to parent if filtering is off, or
* if the entry has children to display, or
* if there is a state change at the current timestamp
*/
if (!fFilterStatus || stateEntry.hasChildren() || interval.getStartTime() == timestamp) {
parent.addChild(stateEntry);
changed = true;
}
} else {
stateEntry.update(interval.getStateValue(),
TmfTimestamp.fromNanos(interval.getStartTime()),
TmfTimestamp.fromNanos(interval.getEndTime()));
// update children recursively
updateStateEntries(ss, fullState, stateEntry, quark, timestamp);
}
}
return changed;
}
private static StateEntry findStateEntry(TmfTreeViewerEntry parent, int quark) {
for (ITmfTreeViewerEntry child : parent.getChildren()) {
StateEntry stateEntry = (StateEntry) child;
if (stateEntry.getQuark() == quark) {
return stateEntry;
}
}
return null;
}
/**
* Set the entries as out of range
*/
private static void markOutOfRange(ITmfTreeViewerEntry parent) {
for (ITmfTreeViewerEntry entry : parent.getChildren()) {
if (entry instanceof StateEntry) {
((StateEntry) 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 static class StateSystemEntry extends TmfTreeViewerEntry {
private final @NonNull ITmfStateSystem fSS;
public StateSystemEntry(@NonNull ITmfStateSystem ss) {
super(ss.getSSID());
fSS = ss;
}
public @NonNull ITmfStateSystem getSS() {
return fSS;
}
}
private static class ModuleEntry extends TmfTreeViewerEntry {
private final @NonNull ITmfAnalysisModuleWithStateSystems fModule;
public ModuleEntry(@NonNull ITmfAnalysisModuleWithStateSystems moduleWithStateSystem) {
super(moduleWithStateSystem.getName());
fModule = moduleWithStateSystem;
}
public @NonNull ITmfAnalysisModuleWithStateSystems getModule() {
return fModule;
}
}
private class StateEntry extends TmfTreeViewerEntry {
private final int fQuark;
private final String fFullPath;
private @NonNull ITmfTimestamp fStart;
private @NonNull ITmfTimestamp fEnd;
private ITmfStateValue fValue;
private boolean fModified;
private boolean fOutOfRange = false;
public StateEntry(String name, int quark, String fullPath, ITmfStateValue value, @NonNull ITmfTimestamp start, @NonNull ITmfTimestamp end, boolean modified) {
super(name);
fQuark = quark;
fFullPath = fullPath;
fStart = start;
fEnd = end;
fValue = value;
fModified = modified;
}
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:
case CUSTOM:
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 CUSTOM:
return Messages.TypeCustom;
case NULL:
default:
return EMPTY_STRING;
}
}
public boolean isModified() {
return fModified;
}
public void update(ITmfStateValue value, @NonNull ITmfTimestamp start, @NonNull ITmfTimestamp end) {
fModified = false;
fOutOfRange = false;
if (!start.equals(fStart)) {
fModified = true;
fStart = start;
fEnd = end;
fValue = value;
}
}
public void setOutOfRange() {
fModified = false;
fOutOfRange = true;
}
}
}