/*******************************************************************************
* 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.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
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.ICache;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.ImmediateInDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.Query;
import org.eclipse.cdt.dsf.concurrent.ThreadSafe;
import org.eclipse.cdt.dsf.concurrent.Transaction;
import org.eclipse.cdt.dsf.ui.concurrent.DisplayDsfExecutor;
import org.eclipse.core.runtime.CoreException;
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 ACPM methods and performs a computation on the
* retrieved data.
* <p>
* This example builds on the {@link AsyncSumDataViewer} example. It
* demonstrates using ACPM to solve the data consistency problem when
* retrieving data from multiple sources asynchronously.
* </p>
*/
@ConfinedToDsfExecutor("fDisplayExecutor")
public class ACPMSumDataViewer 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;
/** Executor to use when retrieving data from data providers */
@ThreadSafe
final private ImmediateInDsfExecutor fDataExecutor;
// The viewer and generator that this content provider using.
final private TableViewer fViewer;
final private DataGeneratorCacheManager[] fDataGeneratorCMs;
final private DataGeneratorCacheManager fSumGeneratorCM;
// Fields used in request cancellation logic.
private List<ValueRequestMonitor> fItemDataRequestMonitors =
new LinkedList<ValueRequestMonitor>();
private Set<Integer> fIndexesToCancel = new HashSet<Integer>();
private int fCancelCallsPending = 0;
private Future<?> fRefreshFuture;
public ACPMSumDataViewer(TableViewer viewer,
ImmediateInDsfExecutor dataExecutor, IDataGenerator[] generators,
IDataGenerator sumGenerator)
{
fViewer = viewer;
fDisplayExecutor = DisplayDsfExecutor.getDisplayDsfExecutor(
fViewer.getTable().getDisplay());
fDataExecutor = dataExecutor;
// Create wrappers for data generators. Don't need to register as
// listeners to generator events because the cache managers ensure data
// are already registered for them.
fDataGeneratorCMs = new DataGeneratorCacheManager[generators.length];
for (int i = 0; i < generators.length; i++) {
fDataGeneratorCMs[i] =
new DataGeneratorCacheManager(fDataExecutor, generators[i]);
}
fSumGeneratorCM =
new DataGeneratorCacheManager(fDataExecutor, 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);
// Need to dispose cache managers that were created in this class. This
// needs to be done on the cache manager's thread.
Query<Object> disposeCacheManagersQuery = new Query<Object>() {
@Override
protected void execute(DataRequestMonitor<Object> rm) {
fSumGeneratorCM.dispose();
for (DataGeneratorCacheManager dataGeneratorCM :
fDataGeneratorCMs)
{
dataGeneratorCM.dispose();
}
rm.setData(new Object());
rm.done();
}
};
fDataExecutor.execute(disposeCacheManagersQuery);
try {
disposeCacheManagersQuery.get();
}
catch (InterruptedException e) {}
catch (ExecutionException e) {}
// Cancel any outstanding data requests.
for (ValueRequestMonitor 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 current count. When a new count is set to viewer, the viewer
* will refresh all items as well.
*/
private void queryItemCount() {
// Create the request monitor to collect the count. This request
// monitor will be completed by the following transaction.
final DataRequestMonitor<Integer> rm =
new DataRequestMonitor<Integer>(fDisplayExecutor, null)
{
@Override
protected void handleSuccess() {
setCountToViewer(getData());
}
@Override
protected void handleRejectedExecutionException() {} // Shutting down, ignore.
};
// Use a transaction, even with a single cache. This will ensure that
// if the cache is reset during processing by an event. The request
// for data will be re-issued.
fDataExecutor.execute(new Runnable() {
@Override
public void run() {
new Transaction<Integer>() {
@Override
protected Integer process()
throws Transaction.InvalidCacheException, CoreException
{
return processCount(this);
}
}.request(rm);
}
});
}
/**
* Perform the count retrieval from the sum data generator.
* @param transaction The ACPM transaction to use for calculation.
* @return Calculated count.
* @throws Transaction.InvalidCacheException {@link Transaction#process}
* @throws CoreException See {@link Transaction#process}
*/
private Integer processCount(Transaction<Integer> transaction)
throws Transaction.InvalidCacheException, CoreException
{
ICache<Integer> countCache = fSumGeneratorCM.getCount();
transaction.validate(countCache);
return countCache.getData();
}
/**
* Set the givne count to the viewer. This will cause the viewer will
* refresh all items' data as well.
* <p>Note: This method must be called in the display thread. </p>
* @param count New count to set to viewer.
*/
private void setCountToViewer(int count) {
if (!fViewer.getTable().isDisposed()) {
fViewer.setItemCount(count);
fViewer.getTable().clearAll();
}
}
/**
* Retrieve the current value for given index.
*/
private void queryValue(final int index) {
// Create the request monitor to collect the value. This request
// monitor will be completed by the following transaction.
final ValueRequestMonitor rm = new ValueRequestMonitor(index) {
@Override
protected void handleCompleted() {
fItemDataRequestMonitors.remove(this);
if (isSuccess()) {
setValueToViewer(index, getData());
}
}
@Override
protected void handleRejectedExecutionException() {
// Shutting down, ignore.
}
};
// Save the value request monitor, to cancel it if the view is
// scrolled.
fItemDataRequestMonitors.add(rm);
// Use a transaction, even with a single cache. This will ensure that
// if the cache is reset during processing by an event. The request
// for data will be re-issued.
fDataExecutor.execute(new Runnable() {
@Override
public void run() {
new Transaction<String>() {
@Override
protected String process()
throws Transaction.InvalidCacheException, CoreException
{
return processValue(this, index);
}
}.request(rm);
}
});
}
/**
* 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);
}
}
/**
* Perform the calculation compose the string with data provider values
* and the sum. This implementation also validates the result.
* @param transaction The ACPM transaction to use for calculation.
* @param index Index of value to calculate.
* @return Calculated value.
* @throws Transaction.InvalidCacheException {@link Transaction#process}
* @throws CoreException See {@link Transaction#process}
*/
private String processValue(Transaction<String> transaction, int index)
throws Transaction.InvalidCacheException, CoreException
{
List<ICache<Integer>> valueCaches =
new ArrayList<ICache<Integer>>(fDataGeneratorCMs.length);
for (DataGeneratorCacheManager dataGeneratorCM : fDataGeneratorCMs) {
valueCaches.add(dataGeneratorCM.getValue(index));
}
// Validate all value caches at once. This executes needed requests
// in parallel.
transaction.validate(valueCaches);
// TODO: evaluate sum generator cache in parallel with value caches.
ICache<Integer> sumCache = fSumGeneratorCM.getValue(index);
transaction.validate(sumCache);
// Compose the string with values, sum, and validation result.
StringBuilder result = new StringBuilder();
int calcSum = 0;
for (ICache<Integer> valueCache : valueCaches) {
if (result.length() != 0) result.append(" + ");
result.append(valueCache.getData());
calcSum += valueCache.getData();
}
result.append(" = ");
result.append(sumCache.getData());
if (calcSum != sumCache.getData()) {
result.append(" !INCORRECT! ");
}
return result.toString();
}
/**
* Dedicated class for data item requests. This class holds the index
* argument so it can be examined when canceling stale requests.
*/
private class ValueRequestMonitor extends DataRequestMonitor<String> {
/** Index is used when canceling stale requests. */
int fIndex;
ValueRequestMonitor(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 (fDataGeneratorCMs == null || fViewer.getTable().isDisposed()) {
return;
}
// Go through the outstanding requests and cancel any that
// are not visible anymore.
for (Iterator<ValueRequestMonitor> itr =
fItemDataRequestMonitors.iterator(); itr.hasNext();)
{
ValueRequestMonitor 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);
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 ACPMSumDataGenerator(executor, generators);
// Create the content provider which will populate the viewer.
ACPMSumDataViewer contentProvider = new ACPMSumDataViewer(
tableViewer, new ImmediateInDsfExecutor(executor),
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();
}
}