Java tutorial
/******************************************************************************* * Copyright (c) 2011 Ericsson * * 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: * Mathieu Denis (mathieu.denis@polymtl.ca) - Generalized version based on LTTng * Bernd Hufmann - Updated to use trace reference in TmfEvent and streaming *******************************************************************************/ package org.eclipse.linuxtools.tmf.ui.views.statistics; import java.util.Vector; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.linuxtools.tmf.event.TmfEvent; import org.eclipse.linuxtools.tmf.event.TmfTimeRange; import org.eclipse.linuxtools.tmf.experiment.TmfExperiment; import org.eclipse.linuxtools.tmf.request.ITmfDataRequest; import org.eclipse.linuxtools.tmf.request.ITmfDataRequest.ExecutionType; import org.eclipse.linuxtools.tmf.request.ITmfEventRequest; import org.eclipse.linuxtools.tmf.request.TmfDataRequest; import org.eclipse.linuxtools.tmf.request.TmfEventRequest; import org.eclipse.linuxtools.tmf.signal.TmfExperimentDisposedSignal; import org.eclipse.linuxtools.tmf.signal.TmfExperimentRangeUpdatedSignal; import org.eclipse.linuxtools.tmf.signal.TmfExperimentSelectedSignal; import org.eclipse.linuxtools.tmf.signal.TmfSignalHandler; import org.eclipse.linuxtools.tmf.trace.ITmfTrace; import org.eclipse.linuxtools.tmf.ui.views.TmfView; import org.eclipse.linuxtools.tmf.ui.views.statistics.model.AbsTmfStatisticsTree; import org.eclipse.linuxtools.tmf.ui.views.statistics.model.ITmfColumnDataProvider; import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfBaseColumnData; import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfBaseColumnDataProvider; import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfBaseStatisticsTree; import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfStatisticsTreeNode; import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfStatisticsTreeRootFactory; import org.eclipse.linuxtools.tmf.ui.views.statistics.model.TmfTreeContentProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; /** * <b><u>TmfStatisticsView</u></b> * <p> * The generic Statistics View displays statistics for any kind of traces. * * It is implemented according to the MVC pattern. - The model is a TmfStatisticsTreeNode built by the State Manager. - The view is built with a * TreeViewer. - The controller that keeps model and view synchronized is an observer of the model. * </p> */ public class TmfStatisticsView extends TmfView { /** * The ID correspond to the package in which this class is embedded */ public static final String ID = "org.eclipse.linuxtools.tmf.ui.views.statistics"; //$NON-NLS-1$ // view name public static final String TMF_STATISTICS_VIEW = "StatisticsView"; //$NON-NLS-1$ // Refresh frequency protected static final Long STATS_INPUT_CHANGED_REFRESH = 5000L; // Default PAGE_SIZE protected static final int PAGE_SIZE = 50000; // For background request // The actual tree to display protected TreeViewer fTreeViewer; // Stores the request to the experiment protected ITmfEventRequest<TmfEvent> fRequest = null; // Update synchronization parameters (used for streaming) protected boolean fStatisticsUpdateBusy = false; protected boolean fStatisticsUpdatePending = false; protected TmfTimeRange fStatisticsUpdateRange = null; protected final Object fStatisticsUpdateSyncObj = new Object(); // Object to store the cursor while waiting for the experiment to load private Cursor fWaitCursor = null; // Stores the number of instance private static int fCountInstance = 0; // Number of this instance. Used as an instance ID private int fInstanceNb; /** * Constructor of a statistics view. * * @param viewName * The name to give to the view. */ public TmfStatisticsView(String viewName) { super(viewName); fCountInstance++; fInstanceNb = fCountInstance; } /** * Default constructor. */ public TmfStatisticsView() { this(TMF_STATISTICS_VIEW); } /* * (non-Javadoc) * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) */ @Override public void createPartControl(Composite parent) { final Vector<TmfBaseColumnData> columnDataList = getColumnDataProvider().getColumnData(); parent.setLayout(new FillLayout()); fTreeViewer = new TreeViewer(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); fTreeViewer.setContentProvider(new TmfTreeContentProvider()); fTreeViewer.getTree().setHeaderVisible(true); fTreeViewer.setUseHashlookup(true); for (final TmfBaseColumnData columnData : columnDataList) { final TreeViewerColumn treeColumn = new TreeViewerColumn(fTreeViewer, columnData.getAlignment()); treeColumn.getColumn().setText(columnData.getHeader()); treeColumn.getColumn().setWidth(columnData.getWidth()); treeColumn.getColumn().setToolTipText(columnData.getTooltip()); if (columnData.getComparator() != null) { treeColumn.getColumn().addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (fTreeViewer.getTree().getSortDirection() == SWT.UP || fTreeViewer.getTree().getSortColumn() != treeColumn.getColumn()) { fTreeViewer.setComparator(columnData.getComparator()); fTreeViewer.getTree().setSortDirection(SWT.DOWN); } else { fTreeViewer.setComparator(new ViewerComparator() { @Override public int compare(Viewer viewer, Object e1, Object e2) { return -1 * columnData.getComparator().compare(viewer, e1, e2); } }); fTreeViewer.getTree().setSortDirection(SWT.UP); } fTreeViewer.getTree().setSortColumn(treeColumn.getColumn()); } }); } treeColumn.setLabelProvider(columnData.getLabelProvider()); } // Handler that will draw the bar charts. fTreeViewer.getTree().addListener(SWT.EraseItem, new Listener() { @Override public void handleEvent(Event event) { if (columnDataList.get(event.index).getPercentageProvider() != null) { TmfStatisticsTreeNode node = (TmfStatisticsTreeNode) event.item.getData(); double percentage = columnDataList.get(event.index).getPercentageProvider().getPercentage(node); if (percentage == 0) { return; } if ((event.detail & SWT.SELECTED) > 0) { Color oldForeground = event.gc.getForeground(); event.gc.setForeground(event.item.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION)); event.gc.fillRectangle(event.x, event.y, event.width, event.height); event.gc.setForeground(oldForeground); event.detail &= ~SWT.SELECTED; } int barWidth = (int) ((fTreeViewer.getTree().getColumn(1).getWidth() - 8) * percentage); int oldAlpha = event.gc.getAlpha(); Color oldForeground = event.gc.getForeground(); Color oldBackground = event.gc.getBackground(); event.gc.setAlpha(64); event.gc.setForeground(event.item.getDisplay().getSystemColor(SWT.COLOR_BLUE)); event.gc.setBackground(event.item.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); event.gc.fillGradientRectangle(event.x, event.y, barWidth, event.height, true); event.gc.drawRectangle(event.x, event.y, barWidth, event.height); event.gc.setForeground(oldForeground); event.gc.setBackground(oldBackground); event.gc.setAlpha(oldAlpha); event.detail &= ~SWT.BACKGROUND; } } }); fTreeViewer.setComparator(columnDataList.get(0).getComparator()); fTreeViewer.getTree().setSortColumn(fTreeViewer.getTree().getColumn(0)); fTreeViewer.getTree().setSortDirection(SWT.DOWN); // Read current data if any available TmfExperiment<?> experiment = TmfExperiment.getCurrentExperiment(); if (experiment != null) { // Insert the statistics data into the tree @SuppressWarnings({ "rawtypes", "unchecked" }) TmfExperimentSelectedSignal<?> signal = new TmfExperimentSelectedSignal(this, experiment); experimentSelected(signal); } } /* * (non-Javadoc) * @see org.eclipse.linuxtools.tmf.ui.views.TmfView#dispose() */ @Override public void dispose() { super.dispose(); if (fWaitCursor != null) { fWaitCursor.dispose(); } // clean the model TmfStatisticsTreeRootFactory.removeAll(); } /* * (non-Javadoc) * @see org.eclipse.ui.part.WorkbenchPart#setFocus() */ @Override public void setFocus() { fTreeViewer.getTree().setFocus(); } /** * Refresh the view. */ public void modelInputChanged(boolean complete) { // Ignore update if disposed if (fTreeViewer.getTree().isDisposed()) return; fTreeViewer.getTree().getDisplay().asyncExec(new Runnable() { @Override public void run() { if (!fTreeViewer.getTree().isDisposed()) fTreeViewer.refresh(); } }); if (complete) { sendPendingUpdate(); } } /** * Called when an experiment request has failed or has been canceled Remove the data retrieved from the experiment from the statistics tree. * * @param name * the experiment name */ public void modelIncomplete(String name) { Object input = fTreeViewer.getInput(); if (input != null && input instanceof TmfStatisticsTreeNode) { // The data from this experiment is invalid and shall be removed to // refresh upon next selection TmfStatisticsTreeRootFactory.removeStatTreeRoot(getTreeID(name)); // Reset synchronization information resetUpdateSynchronization(); modelInputChanged(false); } waitCursor(false); } /** * If the user choose another experiment, the current must be disposed. * * @param signal */ @TmfSignalHandler public void experimentDisposed(TmfExperimentDisposedSignal<? extends TmfEvent> signal) { cancelOngoingRequest(); } /** * Handler called when an experiment is selected. Checks if the experiment has changed and requests the selected experiment if it has not yet been * cached. * * @param signal * contains the information about the selection. */ @TmfSignalHandler public void experimentSelected(TmfExperimentSelectedSignal<? extends TmfEvent> signal) { if (signal != null) { TmfExperiment<?> experiment = signal.getExperiment(); String experimentName = experiment.getName(); if (TmfStatisticsTreeRootFactory.containsTreeRoot(getTreeID(experimentName))) { // The experiment root is already present TmfStatisticsTreeNode experimentTreeNode = TmfStatisticsTreeRootFactory .getStatTreeRoot(getTreeID(experimentName)); @SuppressWarnings("rawtypes") ITmfTrace[] traces = experiment.getTraces(); // check if there is partial data loaded in the experiment int numTraces = experiment.getTraces().length; int numNodeTraces = experimentTreeNode.getNbChildren(); if (numTraces == numNodeTraces) { boolean same = true; // Detect if the experiment contains the same traces as when // previously selected for (int i = 0; i < numTraces; i++) { String traceName = traces[i].getName(); if (!experimentTreeNode.containsChild(traceName)) { same = false; break; } } if (same) { // no need to reload data, all traces are already loaded fTreeViewer.setInput(experimentTreeNode); resetUpdateSynchronization(); return; } experimentTreeNode.reset(); } } else { TmfStatisticsTreeRootFactory.addStatsTreeRoot(getTreeID(experimentName), getStatisticData()); } resetUpdateSynchronization(); TmfStatisticsTreeNode treeModelRoot = TmfStatisticsTreeRootFactory .getStatTreeRoot(getTreeID(experiment.getName())); // if the model has contents, clear to start over if (treeModelRoot.hasChildren()) { treeModelRoot.reset(); } // set input to a clean data model fTreeViewer.setInput(treeModelRoot); // if the data is not available or has changed, reload it requestData(experiment, experiment.getTimeRange()); } } /** * @param signal */ @SuppressWarnings("unchecked") @TmfSignalHandler public void experimentRangeUpdated(TmfExperimentRangeUpdatedSignal signal) { TmfExperiment<TmfEvent> experiment = (TmfExperiment<TmfEvent>) signal.getExperiment(); // validate if (!experiment.equals(TmfExperiment.getCurrentExperiment())) { return; } requestData(experiment, signal.getRange()); } /** * Return the size of the request when performing background request. * * @return the block size for background request. */ protected int getIndexPageSize() { return PAGE_SIZE; } /** * * @return the quantity of data to retrieve before a refresh of the view is performed. */ protected long getInputChangedRefresh() { return STATS_INPUT_CHANGED_REFRESH; } /** * This method can be overridden to implement another way to represent the statistics data and to retrieve the information for display. * * @return a TmfStatisticsData object. */ protected AbsTmfStatisticsTree getStatisticData() { return new TmfBaseStatisticsTree(); } /** * This method can be overridden to change the representation of the data in the columns. * * @return an object implementing ITmfBaseColumnDataProvider. */ protected ITmfColumnDataProvider getColumnDataProvider() { return new TmfBaseColumnDataProvider(); } /** * Construct the ID based on the experiment name * @param experimentName the name of the trace name to show in the view * @return a view ID */ protected String getTreeID(String experimentName) { return experimentName + fInstanceNb; } /** * When the experiment is loading the cursor will be different so the user know the processing is not finished yet. * * @param waitInd * indicates if we need to show the waiting cursor, or the default one */ protected void waitCursor(final boolean waitInd) { if ((fTreeViewer == null) || (fTreeViewer.getTree().isDisposed())) { return; } Display display = fTreeViewer.getControl().getDisplay(); if (fWaitCursor == null) { fWaitCursor = new Cursor(display, SWT.CURSOR_WAIT); } // Perform the updates on the UI thread display.asyncExec(new Runnable() { @Override public void run() { if ((fTreeViewer != null) && (!fTreeViewer.getTree().isDisposed())) { Cursor cursor = null; /* indicates default */ if (waitInd) { cursor = fWaitCursor; } fTreeViewer.getControl().setCursor(cursor); } } }); } /** * Perform the request for an experiment and populates the statistics tree with event. * @param experiment experiment for which we need the statistics data. * @param time range to request */ @SuppressWarnings("unchecked") protected void requestData(final TmfExperiment<?> experiment, TmfTimeRange timeRange) { if (experiment != null) { // Check if update is already ongoing if (checkUpdateBusy(timeRange)) { return; } int index = 0; for (TmfStatisticsTreeNode node : ((TmfStatisticsTreeNode) fTreeViewer.getInput()).getChildren()) { index += (int) node.getValue().nbEvents; } // Preparation of the event request fRequest = new TmfEventRequest<TmfEvent>(TmfEvent.class, timeRange, index, TmfDataRequest.ALL_DATA, getIndexPageSize(), ExecutionType.BACKGROUND) { @Override public void handleData(TmfEvent data) { super.handleData(data); if (data != null) { AbsTmfStatisticsTree statisticsData = TmfStatisticsTreeRootFactory .getStatTree(getTreeID(experiment.getName())); final String traceName = data.getParentTrace().getName(); ITmfExtraEventInfo extraInfo = new ITmfExtraEventInfo() { @Override public String getTraceName() { if (traceName == null) { return Messages.TmfStatisticsView_UnknownTraceName; } return traceName; } }; statisticsData.registerEvent(data, extraInfo); statisticsData.increase(data, extraInfo, 1); // Refresh View if ((getNbRead() % getInputChangedRefresh()) == 0) { modelInputChanged(false); } } } @Override public void handleSuccess() { super.handleSuccess(); modelInputChanged(true); waitCursor(false); } @Override public void handleFailure() { super.handleFailure(); modelIncomplete(experiment.getName()); } @Override public void handleCancel() { super.handleCancel(); modelIncomplete(experiment.getName()); } }; ((TmfExperiment<TmfEvent>) experiment).sendRequest((ITmfDataRequest<TmfEvent>) fRequest); waitCursor(true); } } /** * Cancels the current ongoing request */ protected void cancelOngoingRequest() { if (fRequest != null && !fRequest.isCompleted()) { fRequest.cancel(); } } /** * Reset update synchronization information */ protected void resetUpdateSynchronization() { synchronized (fStatisticsUpdateSyncObj) { fStatisticsUpdateBusy = false; fStatisticsUpdatePending = false; } } /** * Checks if statistic update is ongoing. If it is ongoing the new time range is stored as pending * * @param timeRange - new time range * @return true if statistic update is ongoing else false */ protected boolean checkUpdateBusy(TmfTimeRange timeRange) { synchronized (fStatisticsUpdateSyncObj) { if (fStatisticsUpdateBusy) { fStatisticsUpdatePending = true; fStatisticsUpdateRange = timeRange; return true; } fStatisticsUpdateBusy = true; return false; } } /** * Sends pending request (if any) */ protected void sendPendingUpdate() { synchronized (fStatisticsUpdateSyncObj) { fStatisticsUpdateBusy = false; if (fStatisticsUpdatePending) { fStatisticsUpdatePending = false; requestData(TmfExperiment.getCurrentExperiment(), fStatisticsUpdateRange); } } } }