Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: e95e78f2ba40fbb14b413b3df26501c57710a163 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/*******************************************************************************
 * Copyright (c) 2006, 2011 Wind River Systems and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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);
    }

    @Override
	public void dispose() {
        fDataGenerator.removeListener(this);
    }

    @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.schedule(
            new Runnable() { @Override
			public void run() {
                cancelStaleRequests(topIdx, botIdx);
            }},
            1, TimeUnit.MILLISECONDS);
    }

    /**
     * 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);
    }

    @Override
	@ThreadSafe
    public void countChanged() {
        queryItemCount();
    }

    @Override
	@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() {
            @Override
			public void run() {
                if (!fViewer.getTable().isDisposed()) {
                    for (Integer index : indexes) {
                        tableViewer.clear(index);
                    }
                }
            }});
    }

    /**
     * Retrieve the up to date count.  When a new count is set to viewer, the
     * viewer will refresh all items as well.
     */
    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();
                    }
                }
            });
    }


    /**
     * 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) {
        ValueDataRequestMonitor rm = new ValueDataRequestMonitor(index);
        fItemDataRequestMonitors.add(rm);
        fDataGenerator.getValue(index, rm);
    }

    /**
     * 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<Integer> {

        /** 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 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();
    }
}

Back to the top