/******************************************************************************* * Copyright (c) 2011 Wind River Systems 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: * Wind River Systems - initial API and implementation *******************************************************************************/ //#ifdef exercises package org.eclipse.cdt.examples.dsf.dataviewer; //#else //#package org.eclipse.cdt.examples.dsf.dataviewer.answers; //#endif import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor; import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; import org.eclipse.cdt.dsf.concurrent.DefaultDsfExecutor; import org.eclipse.cdt.dsf.concurrent.DsfExecutor; import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; import org.eclipse.cdt.dsf.concurrent.Query; import org.eclipse.cdt.dsf.concurrent.ThreadSafe; import org.eclipse.cdt.dsf.ui.concurrent.DisplayDsfExecutor; import org.eclipse.jface.viewers.ILazyContentProvider; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Table; /** * Data viewer based on a table, which reads data from multiple data * providers using asynchronous methods and performs a compultation * on the retrieved data. * <p> * This example builds on the {@link AsyncDataViewer} example and * demonstrates the pitfalls of retrieving data from multiple sources * asynchronously: The data is retrieved separate from a set of providers * as well as from a data provider that sums the values from the other * providers. The viewer then performs a check to ensure consistency of * retrieved data. If the retrieved data is inconsistent an "INCORRECT" * label is added in the viewer. * </p> * <p> * This viewer is updated periodically every 10 seconds, instead of being * updated with every change in every data provider, which would overwhelm * the viewer. * </p> */ @ConfinedToDsfExecutor("fDisplayExecutor") public class AsyncSumDataViewer implements ILazyContentProvider { /** View update frequency interval. */ final private static int UPDATE_INTERVAL = 10000; /** Executor to use instead of Display.asyncExec(). **/ @ThreadSafe final private DsfExecutor fDisplayExecutor; // The viewer and generator that this content provider using. final private TableViewer fViewer; final private IDataGenerator[] fDataGenerators; final private IDataGenerator fSumGenerator; // Fields used in request cancellation logic. private List<ValueCountingRequestMonitor> fItemDataRequestMonitors = new LinkedList<ValueCountingRequestMonitor>(); private Set<Integer> fIndexesToCancel = new HashSet<Integer>(); private int fCancelCallsPending = 0; private Future<?> fRefreshFuture; public AsyncSumDataViewer(TableViewer viewer, IDataGenerator[] generators, IDataGenerator sumGenerator) { fViewer = viewer; fDisplayExecutor = DisplayDsfExecutor.getDisplayDsfExecutor( fViewer.getTable().getDisplay()); fDataGenerators = generators; fSumGenerator = sumGenerator; // Schedule a task to refresh the viewer periodically. fRefreshFuture = fDisplayExecutor.scheduleAtFixedRate( new Runnable() { @Override public void run() { queryItemCount(); } }, UPDATE_INTERVAL, UPDATE_INTERVAL, TimeUnit.MILLISECONDS); } @Override public void dispose() { // Cancel the periodic task of refreshing the view. fRefreshFuture.cancel(false); // Cancel any outstanding data requests. for (ValueCountingRequestMonitor rm : fItemDataRequestMonitors) { rm.cancel(); } fItemDataRequestMonitors.clear(); } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // Set the initial count to the viewer after the input is set. queryItemCount(); } @Override public void updateElement(final int index) { // Calculate the visible index range. final int topIdx = fViewer.getTable().getTopIndex(); final int botIdx = topIdx + getVisibleItemCount(topIdx); // Request the item for the given index. queryValue(index); // Invoke a cancel task with a delay. The delay allows multiple cancel // calls to be combined together improving performance of the viewer. fCancelCallsPending++; fDisplayExecutor.execute( new Runnable() { @Override public void run() { cancelStaleRequests(topIdx, botIdx); }}); } /** * Calculates the number of visible items based on the top item index and * table bounds. * @param top Index of top item. * @return calculated number of items in viewer */ private int getVisibleItemCount(int top) { Table table = fViewer.getTable(); int itemCount = table.getItemCount(); return Math.min( (table.getBounds().height / table.getItemHeight()) + 2, itemCount - top); } /** * Retrieve the up to date count. */ private void queryItemCount() { // Note:The count is retrieved from the sum generator only, the sum // generator is responsible for calculating the count based on // individual data providers' counts. fIndexesToCancel.clear(); fSumGenerator.getCount( new DataRequestMonitor<Integer>(fDisplayExecutor, null) { @Override protected void handleSuccess() { setCountToViewer(getData()); } @Override protected void handleRejectedExecutionException() { // Shutting down, ignore. } }); } /** * Set the givne count to the viewer. This will cause the viewer will * refresh all items' data as well. * @param count New count to set to viewer. */ private void setCountToViewer(int count) { if (!fViewer.getTable().isDisposed()) { fViewer.setItemCount(count); fViewer.getTable().clearAll(); } } /** * Retrieves value of an element at given index. When complete the value * is written to the viewer. * @param index Index of value to retrieve. */ private void queryValue(final int index) { // Values retrieved asynchronously from providers are stored in local // arrays. final int[] values = new int[fDataGenerators.length]; final int[] sum = new int[1]; // Counting request monitor is invoked when the required number of // value requests is completed. final ValueCountingRequestMonitor crm = new ValueCountingRequestMonitor(index) { @Override protected void handleCompleted() { fItemDataRequestMonitors.remove(this); // Check if the request completed successfully, otherwise // ignore it. if (isSuccess()) { StringBuilder result = new StringBuilder(); int calcSum = 0; for (int value : values) { if (result.length() != 0) result.append(" + "); result.append(value); calcSum += value; } result.append(" = "); result.append(sum[0]); if (calcSum != sum[0]) { result.append(" !INCORRECT! "); } setValueToViewer(fIndex, result.toString()); } }; }; // Request data from each data generator. for (int i = 0; i < fDataGenerators.length; i++) { final int finalI = i; fDataGenerators[i].getValue( index, // Use the display executor to construct the request monitor, // this will cause the handleCompleted() method to be // automatically called on the display thread. new DataRequestMonitor<Integer>( ImmediateExecutor.getInstance(), crm) { @Override protected void handleSuccess() { values[finalI] = getData(); crm.done(); } }); } // Separately request data from the sum data generator. fSumGenerator.getValue( index, new DataRequestMonitor<Integer>( ImmediateExecutor.getInstance(), crm) { @Override protected void handleSuccess() { sum[0] = getData(); crm.done(); } }); crm.setDoneCount(fDataGenerators.length + 1); fItemDataRequestMonitors.add(crm); } /** * Write the view value to the viewer. * <p>Note: This method must be called in the display thread. </p> * @param index Index of value to set. * @param value New value. */ private void setValueToViewer(int index, String value) { if (!fViewer.getTable().isDisposed()) { fViewer.replace(value, index); } } /** * Dedicated class for data item requests. This class holds the index * argument so it can be examined when canceling stale requests. */ private class ValueCountingRequestMonitor extends CountingRequestMonitor { /** Index is used when canceling stale requests. */ int fIndex; ValueCountingRequestMonitor(int index) { super(fDisplayExecutor, null); fIndex = index; } @Override protected void handleRejectedExecutionException() { // Shutting down, ignore. } } /** * Cancels any outstanding value requests for items which are no longer * visible in the viewer. * * @param topIdx Index of top visible item in viewer. * @param botIdx Index of bottom visible item in viewer. */ private void cancelStaleRequests(int topIdx, int botIdx) { // Decrement the count of outstanding cancel calls. fCancelCallsPending--; // Must check again, in case disposed while re-dispatching. if (fDataGenerators == null || fViewer.getTable().isDisposed()) return; // Go through the outstanding requests and cancel any that // are not visible anymore. for (Iterator<ValueCountingRequestMonitor> itr = fItemDataRequestMonitors.iterator(); itr.hasNext();) { ValueCountingRequestMonitor item = itr.next(); if (item.fIndex < topIdx || item.fIndex > botIdx) { // Set the item to canceled status, so that the data provider // will ignore it. item.cancel(); // Add the item index to list of indexes that were canceled, // which will be sent to the table widget. fIndexesToCancel.add(item.fIndex); // Remove the item from the outstanding cancel requests. itr.remove(); } } if (!fIndexesToCancel.isEmpty() && fCancelCallsPending == 0) { Set<Integer> canceledIdxs = fIndexesToCancel; fIndexesToCancel = new HashSet<Integer>(); // Clear the indexes of the canceled request, so that the // viewer knows to request them again when needed. // Note: clearing using TableViewer.clear(int) seems very // inefficient, it's better to use Table.clear(int[]). int[] canceledIdxsArray = new int[canceledIdxs.size()]; int i = 0; for (Integer index : canceledIdxs) { canceledIdxsArray[i++] = index; } fViewer.getTable().clear(canceledIdxsArray); } } /** * The entry point for the example. * @param args Program arguments. */ public static void main(String[] args) { // Create the shell to hold the viewer. Display display = new Display(); Shell shell = new Shell(display, SWT.SHELL_TRIM); shell.setLayout(new GridLayout()); GridData data = new GridData(GridData.FILL_BOTH); shell.setLayoutData(data); Font font = new Font(display, "Courier", 10, SWT.NORMAL); // Create the table viewer. TableViewer tableViewer = new TableViewer(shell, SWT.BORDER | SWT.VIRTUAL); tableViewer.getControl().setLayoutData(data); // Single executor (and single thread) is used by all data generators, // including the sum generator. DsfExecutor executor = new DefaultDsfExecutor("Example executor"); // Create the data generator. final IDataGenerator[] generators = new IDataGenerator[5]; for (int i = 0; i < generators.length; i++) { generators[i] = new DataGeneratorWithExecutor(executor); } final IDataGenerator sumGenerator = new AsyncSumDataGenerator(executor, generators); // Create the content provider which will populate the viewer. AsyncSumDataViewer contentProvider = new AsyncSumDataViewer(tableViewer, generators, sumGenerator); tableViewer.setContentProvider(contentProvider); tableViewer.setInput(new Object()); // Open the shell and service the display dispatch loop until user // closes the shell. shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } // The IDataGenerator.shutdown() method is asynchronous, this requires // using a query again in order to wait for its completion. Query<Object> shutdownQuery = new Query<Object>() { @Override protected void execute(DataRequestMonitor<Object> rm) { CountingRequestMonitor crm = new CountingRequestMonitor( ImmediateExecutor.getInstance(), rm); for (int i = 0; i < generators.length; i++) { generators[i].shutdown(crm); } sumGenerator.shutdown(crm); crm.setDoneCount(generators.length); } }; executor.execute(shutdownQuery); try { shutdownQuery.get(); } catch (Exception e) {} // Shut down the display. font.dispose(); display.dispose(); } }