/******************************************************************************* * Copyright (c) 2006, 2009 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.TimeUnit; import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor; import org.eclipse.cdt.dsf.concurrent.ThreadSafe; import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor; 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.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 using asynchronous methods. * <p> * This viewer implements the {@link ILazyContentProvider} interface * which is used by the JFace TableViewer class to populate a Table. This * interface contains separate asynchronous methods for requesting the count * and values for individual indexes, which neatly correspond to the methods * in {@link IDataGenerator}. As an added optimization, this viewer * implementation checks for the range of visible items in the view upon each * request, and it cancels old requests which scroll out of view but have not * been completed yet. However, it is up to the data generator implementation * to check the canceled state of the requests and ignore them. * </p> */ @ConfinedToDsfExecutor("fDisplayExecutor") public class AsyncDataViewer implements ILazyContentProvider, IDataGenerator.Listener { // 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 fDataGenerator; // Fields used in request cancellation logic. private List<ValueDataRequestMonitor> fItemDataRequestMonitors = new LinkedList<ValueDataRequestMonitor>(); private Set<Integer> fIndexesToCancel = new HashSet<Integer>(); private int fCancelCallsPending = 0; public AsyncDataViewer(TableViewer viewer, IDataGenerator generator) { fViewer = viewer; fDisplayExecutor = DisplayDsfExecutor.getDisplayDsfExecutor(fViewer.getTable().getDisplay()); fDataGenerator = generator; fDataGenerator.addListener(this); } public void dispose() { fDataGenerator.removeListener(this); } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // Set the initial count to the viewer after the input is set. queryItemCount(); } 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.schedule( new Runnable() { public void run() { cancelStaleRequests(topIdx, botIdx); }}, 1, TimeUnit.MILLISECONDS); } private int getVisibleItemCount(int top) { Table table = fViewer.getTable(); int itemCount = table.getItemCount(); return Math.min((table.getBounds().height / table.getItemHeight()) + 2, itemCount - top); } @ThreadSafe public void countChanged() { queryItemCount(); } @ThreadSafe public void valuesChanged(final Set<Integer> indexes) { // Mark the changed items in table viewer as dirty, this will // trigger update requests for these indexes if they are // visible in the viewer. final TableViewer tableViewer = fViewer; fDisplayExecutor.execute( new Runnable() { public void run() { if (!fViewer.getTable().isDisposed()) { for (Integer index : indexes) { tableViewer.clear(index); } } }}); } private void queryItemCount() { // Request count from data provider. When the count is returned, we // have to re-dispatch into the display thread to avoid calling // the table widget on the DSF dispatch thread. fIndexesToCancel.clear(); fDataGenerator.getCount( // 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>(fDisplayExecutor, null) { @Override protected void handleCompleted() { if (!fViewer.getTable().isDisposed()) { fViewer.setItemCount(getData()); fViewer.getTable().clearAll(); } } }); } // Dedicated class for data item requests. This class holds the index // argument so it can be examined when canceling stale requests. private class ValueDataRequestMonitor extends DataRequestMonitor<String> { /** Index is used when canceling stale requests. */ int fIndex; ValueDataRequestMonitor(int index) { super(fDisplayExecutor, null); fIndex = index; } @Override protected void handleCompleted() { fItemDataRequestMonitors.remove(this); // Check if the request completed successfully, otherwise ignore it. if (isSuccess()) { if (!fViewer.getTable().isDisposed()) { fViewer.replace(getData(), fIndex); } } } } private void queryValue(final int index) { ValueDataRequestMonitor rm = new ValueDataRequestMonitor(index); fItemDataRequestMonitors.add(rm); fDataGenerator.getValue(index, rm); } 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 (fDataGenerator == null || fViewer.getTable().isDisposed()) return; // Go through the outstanding requests and cancel any that // are not visible anymore. for (Iterator<ValueDataRequestMonitor> itr = fItemDataRequestMonitors.iterator(); itr.hasNext();) { ValueDataRequestMonitor 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); } } 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); // Create the data generator. final IDataGenerator generator = new DataGeneratorWithExecutor(); // Create the content provider which will populate the viewer. AsyncDataViewer contentProvider = new AsyncDataViewer(tableViewer, generator); 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) { generator.shutdown(rm); } }; ImmediateExecutor.getInstance().execute(shutdownQuery); try { shutdownQuery.get(); } catch (Exception e) {} // Shut down the display. font.dispose(); display.dispose(); } }