/**
* (c) 2011, Alejandro Serrano
* Released under the terms of the EPL.
*/
package net.sf.eclipsefp.haskell.profiler.internal.editors;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import net.sf.eclipsefp.haskell.profiler.ProfilerPlugin;
import net.sf.eclipsefp.haskell.profiler.internal.util.UITexts;
import net.sf.eclipsefp.haskell.profiler.model.Job;
import net.sf.eclipsefp.haskell.profiler.model.Sample;
import org.eclipse.birt.chart.model.Chart;
import org.eclipse.birt.chart.model.ChartWithAxes;
import org.eclipse.birt.chart.model.attribute.LegendItemType;
import org.eclipse.birt.chart.model.attribute.TickStyle;
import org.eclipse.birt.chart.model.component.Axis;
import org.eclipse.birt.chart.model.component.Series;
import org.eclipse.birt.chart.model.component.impl.SeriesImpl;
import org.eclipse.birt.chart.model.data.NumberDataSet;
import org.eclipse.birt.chart.model.data.SeriesDefinition;
import org.eclipse.birt.chart.model.data.impl.NumberDataSetImpl;
import org.eclipse.birt.chart.model.data.impl.SeriesDefinitionImpl;
import org.eclipse.birt.chart.model.impl.ChartWithAxesImpl;
import org.eclipse.birt.chart.model.type.AreaSeries;
import org.eclipse.birt.chart.model.type.impl.AreaSeriesImpl;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Scale;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.dialogs.ContainerGenerator;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.ide.FileStoreEditorInput;
/**
* Viewer for profiling output generated by GHC.
* Uses the BIRT Charting engine.
* @author Alejandro Serrano
*/
public abstract class ProfileViewerBirtImpl extends ProfileViewerImpl{
static int INITIAL_NUMBER_OF_ITEMS = 15;
Job job = null;
double[] samplePoints;
List<Map.Entry<String, BigInteger>> entries;
private FormToolkit toolkit;
private ScrolledForm form;
private Scale slider;
private ChartCanvas canvas;
private IEditorInput input;
private String getFileContents(IEditorInput input) throws Exception{
InputStream contents=null;
try {
if (input instanceof FileStoreEditorInput) {
FileStoreEditorInput fInput = (FileStoreEditorInput) input;
URI path = fInput.getURI();
contents = new FileInputStream(new File(path));
setPartName(fInput.getName());
} else {
IFileEditorInput fInput = (IFileEditorInput) input;
IFile inputFile = fInput.getFile();
setPartName(inputFile.getName());
contents = inputFile.getContents();
}
return new Scanner(contents).useDelimiter("\\Z").next();
} finally {
contents.close();
}
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
this.input=input;
try {
job = Job.parse(new StringReader(getFileContents(input)));
// Sort entries
entries = job.sortEntriesByTotal();
// Get sample points
samplePoints = new double[job.getSamplesAndTimes().size()];
int i = 0;
for (Sample s : job.getSamples()) {
// #158: round times
samplePoints[i] = new BigDecimal(new Float(s.getTime()).doubleValue()).setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue();
i++;
}
} catch (Exception e) {
ProfilerPlugin.log(IStatus.ERROR, e.getLocalizedMessage(), e);
throw new PartInitException(Status.CANCEL_STATUS);
}
}
@Override
public void createPartControl(Composite parent) {
toolkit = new FormToolkit(parent.getDisplay());
form = toolkit.createScrolledForm(parent);
form.setText(NLS.bind(UITexts.graph_title, new Object[] { job.getName(), job.getDate() }));
int n = entries.size() < INITIAL_NUMBER_OF_ITEMS ? entries.size() : INITIAL_NUMBER_OF_ITEMS;
GridLayout layout = new GridLayout();
layout.numColumns = 1;
form.getBody().setLayout(layout);
final Chart chart = createChart();
populateChart(chart, n);
canvas = new ChartCanvas(form.getBody(), SWT.NONE);
canvas.setLayoutData(new GridData(GridData.FILL_BOTH));
canvas.setChart(chart);
Section section = toolkit.createSection(form.getBody(),
ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE);
section.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
section.setText(UITexts.graph_options);
Composite sectionClient = toolkit.createComposite(section);
GridLayout hLayout = new GridLayout();
hLayout.numColumns = 2;
sectionClient.setLayout(hLayout);
section.setClient(sectionClient);
toolkit.createLabel(sectionClient, UITexts.graph_ungroupElements);
slider = new Scale(sectionClient, SWT.NONE);
slider.setMinimum(0);
slider.setMaximum(entries.size());
slider.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
slider.setSelection(n);
slider.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
populateChart(chart, slider.getSelection());
canvas.rebuildChart();
canvas.redraw();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
populateChart(chart, slider.getSelection());
canvas.rebuildChart();
canvas.redraw();
}
});
toolkit.paintBordersFor(parent);
}
private Chart createChart(){
Chart chart = ChartWithAxesImpl.create();
// Title
// chart.getTitle().getLabel().getCaption().setValue(job.getName());
chart.getTitle().setVisible(false);
// chart.getTitle().getLabel().getCaption().getFont().setSize(14);
// chart.getTitle().getLabel().getCaption().getFont().setName("Arial");
// Legend
chart.getLegend().setItemType(LegendItemType.SERIES_LITERAL);
chart.getLegend().setVisible(true);
// X-Axis -> time
Axis xAxis = ((ChartWithAxes) chart).getPrimaryBaseAxes()[0];
// xAxis.setType(AxisType.LINEAR_LITERAL);
xAxis.getMajorGrid().setTickStyle(TickStyle.BELOW_LITERAL);
xAxis.getTitle().setVisible(true);
xAxis.getTitle().getCaption().setValue(job.getSampleUnit());
xAxis.getLabel().setVisible(true);
// X-Axis data
NumberDataSet xDataSet = NumberDataSetImpl.create(samplePoints);
Series xCategory = SeriesImpl.create();
xCategory.setDataSet(xDataSet);
SeriesDefinition sdX = SeriesDefinitionImpl.create();
sdX.getSeriesPalette().shift(0);
xAxis.getSeriesDefinitions().add(sdX);
sdX.getSeries().add(xCategory);
// Y-Axis -> memory
Axis yAxis = ((ChartWithAxes) chart).getPrimaryOrthogonalAxis(xAxis);
yAxis.getMajorGrid().setTickStyle(TickStyle.LEFT_LITERAL);
yAxis.getMajorGrid().getLineAttributes().setVisible(true);
yAxis.getMinorGrid().getLineAttributes().setVisible(true);
yAxis.setPercent(false);
yAxis.getTitle().getCaption().setValue(job.getValueUnit());
yAxis.getTitle().setVisible(true);
yAxis.getTitle().getCaption().getFont().setRotation(90);
yAxis.getLabel().setVisible(true);
// Y-Axis data
SeriesDefinition sdY = SeriesDefinitionImpl.create();
sdY.getSeriesPalette().shift(1);
yAxis.getSeriesDefinitions().add(sdY);
return chart;
}
private void populateChart(Chart chart,int numberApart) {
int n = entries.size() < numberApart ? entries.size() : numberApart;
List<Map.Entry<String, BigInteger>> entriesApart = new ArrayList<>(
entries.subList(0, n));
Axis xAxis = ((ChartWithAxes) chart).getPrimaryBaseAxes()[0];
Axis yAxis = ((ChartWithAxes) chart).getPrimaryOrthogonalAxis(xAxis);
SeriesDefinition sdY=yAxis.getSeriesDefinitions().get(0);
sdY.getSeries().clear();
// Get the numbers
ProfileNumbers numbers = new ProfileNumbers(entriesApart, samplePoints.length);
numbers.fillIn(job);
// Add (rest) elements
NumberDataSet restDataSet = NumberDataSetImpl.create(numbers.getRest());
AreaSeries restSeries = (AreaSeries) AreaSeriesImpl.create();
restSeries.setSeriesIdentifier(UITexts.graph_restOfTrace);
restSeries.setDataSet(restDataSet);
restSeries.getLineAttributes().setVisible(false);
restSeries.getLabel().setVisible(false);
restSeries.setStacked(true);
sdY.getSeries().add(restSeries);
// Add apart elements, in reverse order
Collections.reverse(entriesApart);
for (Map.Entry<String, BigInteger> entry : entriesApart) {
double[] entryNumbers = numbers.getEntries().get(entry.getKey());
NumberDataSet entryDataSet = NumberDataSetImpl.create(entryNumbers);
AreaSeries entrySeries = (AreaSeries) AreaSeriesImpl.create();
entrySeries.setSeriesIdentifier(entry.getKey());
entrySeries.setDataSet(entryDataSet);
entrySeries.getLineAttributes().setVisible(false);
entrySeries.getLabel().setVisible(false);
entrySeries.setStacked(true);
sdY.getSeries().add(entrySeries);
}
}
@Override
public void doSaveAs(Shell shell,String partName) {
SaveAsDialog dialog = new SaveAsDialog(shell);
dialog.setOriginalName(partName);
if (dialog.open() == Window.OK) {
try {
IPath path = dialog.getResult();
IPath folder = path.uptoSegment(path.segmentCount() - 1);
ContainerGenerator gen = new ContainerGenerator(folder);
IContainer con = gen.generateContainer(null);
IFile file = con.getFile(Path.fromPortableString(path.lastSegment()));
byte[] bytes = getFileContents(input).getBytes();
try (InputStream source = new ByteArrayInputStream(bytes)) {
if (!file.exists()) {
file.create(source, IResource.NONE, null);
} else {
file.setContents(source, IResource.NONE, null);
}
}
setPartName(path.lastSegment());
} catch (Exception e) {
// Do nothing
}
}
}
@Override
public boolean isSaveAsAllowed() {
return true;
}
}