/*******************************************************************************
* 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:
* Geneviève Bastien - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.views.xychart;
import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model.ITmfXmlModelFactory;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model.ITmfXmlStateAttribute;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model.TmfXmlLocation;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model.readonly.TmfXmlReadOnlyModelFactory;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.IXmlStateSystemContainer;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.XmlUtils;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.stateprovider.TmfXmlStrings;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.Activator;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.TmfXmlUiStrings;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.ui.views.XmlViewInfo;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
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.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;
import org.eclipse.tracecompass.tmf.ui.viewers.xycharts.linecharts.TmfCommonXLineChartViewer;
import org.w3c.dom.Element;
/**
* Main viewer to display XML-defined xy charts. It uses an XML
* {@link TmfXmlUiStrings#XY_VIEW} element from an XML file. This element
* defines which entries from the state system will be shown and also gives
* additional information on the presentation of the view.
*
* @author Geneviève Bastien
*/
public class XmlXYViewer extends TmfCommonXLineChartViewer {
private static final String SPLIT_STRING = "/"; //$NON-NLS-1$
/** Timeout between updates in the updateData thread */
private static final long BUILD_UPDATE_TIMEOUT = 500;
private static final Pattern WILDCARD_PATTERN = Pattern.compile("\\*"); //$NON-NLS-1$
private final ITmfXmlModelFactory fFactory = TmfXmlReadOnlyModelFactory.getInstance();
private final Map<Integer, SeriesData> fSeriesData = new HashMap<>();
private final XmlViewInfo fViewInfo;
/** XML Model elements to use to create the series */
private @Nullable ITmfXmlStateAttribute fDisplay;
private @Nullable ITmfXmlStateAttribute fSeriesName;
private @Nullable XmlXYEntry fEntry;
private enum DisplayType {
ABSOLUTE,
DELTA
}
/**
* The information related to one series on the chart
*/
private class SeriesData {
private final double[] fYValues;
private final double @Nullable [] fYAbsoluteValues;
private final Integer fDisplayQuark;
private final String fName;
private final DisplayType fType;
public SeriesData(int length, int attributeQuark, String seriesName, DisplayType type) {
fYValues = new double[length];
fDisplayQuark = attributeQuark;
fName = seriesName;
fType = type;
switch (fType) {
case DELTA:
fYAbsoluteValues = new double[length];
break;
case ABSOLUTE:
default:
fYAbsoluteValues = null;
break;
}
}
public double[] getYValues() {
return fYValues;
}
public Integer getDisplayQuark() {
return fDisplayQuark;
}
public String getSeriesName() {
return fName;
}
public void setYValue(int i, double yvalue) {
switch (fType) {
case DELTA:
double[] absoluteVals = fYAbsoluteValues;
if (absoluteVals == null) {
throw new IllegalStateException();
}
absoluteVals[i] = yvalue;
/*
* At the first timestamp, the delta value should be 0 since we
* do not have the previous values
*/
double prevValue = yvalue;
if (i > 0) {
prevValue = absoluteVals[i - 1];
}
fYValues[i] = yvalue - prevValue;
break;
case ABSOLUTE:
default:
fYValues[i] = yvalue;
break;
}
}
}
private static class XmlXYEntry implements IXmlStateSystemContainer {
private final ITmfStateSystem fStateSystem;
private final String fPath;
private final DisplayType fType;
public XmlXYEntry(ITmfStateSystem stateSystem, String path, Element entryElement) {
fStateSystem = stateSystem;
fPath = path;
switch (entryElement.getAttribute(TmfXmlUiStrings.DISPLAY_TYPE)) {
case TmfXmlUiStrings.DISPLAY_TYPE_DELTA:
fType = DisplayType.DELTA;
break;
case TmfXmlUiStrings.DISPLAY_TYPE_ABSOLUTE:
default:
fType = DisplayType.ABSOLUTE;
break;
}
}
@Override
public @Nullable String getAttributeValue(@Nullable String name) {
return name;
}
@Override
public ITmfStateSystem getStateSystem() {
return fStateSystem;
}
@Override
public @NonNull Iterable<@NonNull TmfXmlLocation> getLocations() {
return Collections.EMPTY_SET;
}
public DisplayType getType() {
return fType;
}
public List<Integer> getQuarks() {
/* Get the list of quarks to process with this path */
String[] paths = fPath.split(SPLIT_STRING);
List<Integer> quarks = Collections.singletonList(IXmlStateSystemContainer.ROOT_QUARK);
for (String path : paths) {
List<Integer> subQuarks = new LinkedList<>();
/* Replace * by .* to have a regex string */
String name = WILDCARD_PATTERN.matcher(path).replaceAll(".*"); //$NON-NLS-1$
for (int relativeQuark : quarks) {
subQuarks.addAll(fStateSystem.getSubAttributes(relativeQuark, false, name));
}
quarks = subQuarks;
}
return quarks;
}
}
/**
* Constructor
*
* @param parent
* parent view
* @param viewInfo
* The view info object
*/
public XmlXYViewer(@Nullable Composite parent, XmlViewInfo viewInfo) {
super(parent, Messages.XmlXYViewer_DefaultViewerTitle, Messages.XmlXYViewer_DefaultXAxis, Messages.XmlXYViewer_DefaultYAxis);
fViewInfo = viewInfo;
}
@Override
protected void updateData(long start, long end, int nb, @Nullable IProgressMonitor monitor) {
if (!fViewInfo.waitForInitialization()) {
return;
}
ITmfXmlStateAttribute display = fDisplay;
ITmfXmlStateAttribute seriesNameAttrib = fSeriesName;
XmlXYEntry entry = fEntry;
if (getTrace() == null || display == null || entry == null) {
return;
}
ITmfStateSystem ss = entry.getStateSystem();
double[] xvalues = getXAxis(start, end, nb);
setXAxis(xvalues);
boolean complete = false;
long currentEnd = start;
while (!complete && currentEnd < end) {
if (monitor != null && monitor.isCanceled()) {
return;
}
complete = ss.waitUntilBuilt(BUILD_UPDATE_TIMEOUT);
currentEnd = ss.getCurrentEndTime();
try {
List<Integer> quarks = entry.getQuarks();
long traceStart = getStartTime();
long traceEnd = getEndTime();
long offset = this.getTimeOffset();
/* Initialize quarks and series names */
for (int quark : quarks) {
String seriesName = null;
if (seriesNameAttrib == null) {
seriesName = ss.getAttributeName(quark);
} else {
int seriesNameQuark = seriesNameAttrib.getAttributeQuark(quark, null);
try {
ITmfStateValue seriesNameValue = ss.querySingleState(start, seriesNameQuark).getStateValue();
if (!seriesNameValue.isNull()) {
seriesName = seriesNameValue.toString();
}
if (seriesName == null || seriesName.isEmpty()) {
seriesName = ss.getAttributeName(quark);
}
} catch (TimeRangeException e) {
/*
* The attribute did not exist at this point, simply
* use attribute name as series name
*/
seriesName = ss.getAttributeName(quark);
}
}
fSeriesData.put(quark, new SeriesData(xvalues.length, display.getAttributeQuark(quark, null), seriesName, entry.getType()));
}
for (int i = 0; i < xvalues.length; i++) {
if (monitor != null && monitor.isCanceled()) {
return;
}
double x = xvalues[i];
long time = (long) x + offset;
// make sure that time is in the trace range after double to
// long conversion
time = time < traceStart ? traceStart : time;
time = time > traceEnd ? traceEnd : time;
for (int quark : quarks) {
double yvalue = 0.0;
SeriesData data = checkNotNull(fSeriesData.get(quark));
try {
ITmfStateValue value = ss.querySingleState(time, data.getDisplayQuark()).getStateValue();
switch (value.getType()) {
case DOUBLE:
yvalue = value.unboxDouble();
break;
case LONG:
yvalue = value.unboxLong();
break;
case INTEGER:
yvalue = value.unboxInt();
break;
case NULL:
case STRING:
case CUSTOM:
default:
break;
}
data.setYValue(i, yvalue);
} catch (TimeRangeException e) {
data.setYValue(i, 0);
}
}
}
for (int quark : quarks) {
SeriesData data = checkNotNull(fSeriesData.get(quark));
setSeries(data.getSeriesName(), data.getYValues());
}
updateDisplay();
} catch (StateValueTypeException e) {
Activator.logError("Error updating the data of XML XY view", e); //$NON-NLS-1$
} catch (StateSystemDisposedException e) {
return;
}
}
}
@Override
protected void initializeDataSource() {
super.initializeDataSource();
ITmfTrace trace = this.getTrace();
if (trace == null) {
return;
}
Element viewElement = fViewInfo.getViewElement(TmfXmlUiStrings.XY_VIEW);
if (viewElement == null) {
return;
}
Iterable<String> analysisIds = fViewInfo.getViewAnalysisIds(viewElement);
List<ITmfAnalysisModuleWithStateSystems> stateSystemModules = new LinkedList<>();
if (!analysisIds.iterator().hasNext()) {
/*
* No analysis specified, take all state system analysis modules
*/
for (ITmfAnalysisModuleWithStateSystems module : TmfTraceUtils.getAnalysisModulesOfClass(trace, ITmfAnalysisModuleWithStateSystems.class)) {
stateSystemModules.add(module);
}
} else {
for (String moduleId : analysisIds) {
ITmfAnalysisModuleWithStateSystems module = TmfTraceUtils.getAnalysisModuleOfClass(trace, ITmfAnalysisModuleWithStateSystems.class, moduleId);
if (module != null) {
stateSystemModules.add(module);
}
}
}
/** Initialize the data */
fDisplay = null;
fSeriesName = null;
ITmfStateSystem ss = null;
fEntry = null;
/* Schedule all state systems */
for (ITmfAnalysisModuleWithStateSystems module : stateSystemModules) {
IStatus status = module.schedule();
if (!status.isOK()) {
return;
}
if (!module.waitForInitialization()) {
return;
}
for (ITmfStateSystem ssq : module.getStateSystems()) {
ss = ssq;
break;
}
}
if (ss == null) {
return;
}
/*
* Initialize state attributes. There should be only one entry element
* for XY charts.
*/
List<Element> entries = XmlUtils.getChildElements(viewElement, TmfXmlUiStrings.ENTRY_ELEMENT);
Element entryElement = entries.get(0);
String path = entryElement.getAttribute(TmfXmlUiStrings.PATH);
if (path.isEmpty()) {
path = TmfXmlStrings.WILDCARD;
}
XmlXYEntry entry = new XmlXYEntry(ss, path, entryElement);
fEntry = entry;
/* Get the display element to use */
List<@NonNull Element> displayElements = XmlUtils.getChildElements(entryElement, TmfXmlUiStrings.DISPLAY_ELEMENT);
if (displayElements.isEmpty()) {
Activator.logWarning(String.format("XML view: entry for %s should have a display element", path)); //$NON-NLS-1$
return;
}
Element displayElement = displayElements.get(0);
fDisplay = fFactory.createStateAttribute(displayElement, entry);
/* Get the series name element to use */
List<Element> seriesNameElements = XmlUtils.getChildElements(entryElement, TmfXmlUiStrings.NAME_ELEMENT);
if (!seriesNameElements.isEmpty()) {
Element seriesNameElement = seriesNameElements.get(0);
fSeriesName = fFactory.createStateAttribute(seriesNameElement, entry);
}
}
/**
* Tells the viewer that the view info has been updated and the viewer needs
* to be reinitialized
*/
public void viewInfoUpdated() {
reinitialize();
}
}