Mercurial > projects > dwt-addons
comparison dwtx/jface/viewers/deferred/BackgroundContentProvider.d @ 10:b6c35faf97c8
Viewers
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Mon, 31 Mar 2008 00:47:19 +0200 |
parents | |
children | ea8ff534f622 |
comparison
equal
deleted
inserted
replaced
9:6c14e54dfc11 | 10:b6c35faf97c8 |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2005, 2006 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.jface.viewers.deferred.BackgroundContentProvider; | |
14 | |
15 import dwtx.jface.viewers.deferred.ConcurrentTableUpdator; | |
16 import dwtx.jface.viewers.deferred.IConcurrentModel; | |
17 import dwtx.jface.viewers.deferred.IConcurrentModelListener; | |
18 import dwtx.jface.viewers.deferred.ChangeQueue; | |
19 import dwtx.jface.viewers.deferred.FastProgressReporter; | |
20 import dwtx.jface.viewers.deferred.AbstractVirtualTable; | |
21 import dwtx.jface.viewers.deferred.LazySortedCollection; | |
22 | |
23 import dwtx.core.runtime.Assert; | |
24 import dwtx.core.runtime.IProgressMonitor; | |
25 import dwtx.core.runtime.NullProgressMonitor; | |
26 import dwtx.jface.resource.JFaceResources; | |
27 | |
28 import dwtx.jface.viewers.AcceptAllFilter; | |
29 import dwtx.jface.viewers.IFilter; | |
30 | |
31 import dwt.dwthelper.utils; | |
32 import tango.core.Thread; | |
33 | |
34 /** | |
35 * Contains the algorithm for performing background sorting and filtering in a virtual | |
36 * table. This is the real implementation for <code>DeferredContentProvider</code>. | |
37 * However, this class will work with anything that implements <code>AbstractVirtualTable</code> | |
38 * rather than being tied to a <code>TableViewer</code>. | |
39 * | |
40 * <p> | |
41 * This is package visiblity since it currently only needs to be used in one place, | |
42 * but it could potentially be made public if there was a need to use the same background | |
43 * sorting algorithm for something other than a TableViewer. | |
44 * </p> | |
45 * | |
46 * <p> | |
47 * Information flow is like this: | |
48 * </p> | |
49 * <ol> | |
50 * <li>IConcurrentModel sends unordered elements to BackgroundContentProvider (in a background thread)</li> | |
51 * <li>BackgroundContentProvider sorts, filters, and sends element/index pairs to | |
52 * ConcurrentTableUpdator (in a background thread)</li> | |
53 * <li>ConcurrentTableUpdator batches the updates and sends them to an AbstractVirtualTable | |
54 * (in the UI thread)</li> | |
55 * </ol> | |
56 * | |
57 * <p> | |
58 * Internally, sorting is done using a <code>LazySortedCollection</code>. This data structure | |
59 * allows the content provider to locate and sort the visible range without fully sorting | |
60 * all elements in the table. It also supports fast cancellation, allowing the visible range | |
61 * to change in the middle of a sort without discarding partially-sorted information from | |
62 * the previous range. | |
63 * </p> | |
64 * | |
65 * @since 3.1 | |
66 */ | |
67 /* package */ final class BackgroundContentProvider { | |
68 | |
69 /** | |
70 * Sorting message string | |
71 */ | |
72 private static const String SORTING; | |
73 static this(){ | |
74 SORTING = JFaceResources.getString("Sorting"); //$NON-NLS-1$ | |
75 } | |
76 /** | |
77 * Table limit. -1 if unlimited | |
78 */ | |
79 private int limit = -1; | |
80 | |
81 /** | |
82 * Model that is currently providing input to this content provider. | |
83 */ | |
84 private IConcurrentModel model; | |
85 | |
86 /** | |
87 * Current sort order | |
88 */ | |
89 private /+volatile+/ Comparator sortOrder; | |
90 | |
91 /** | |
92 * True iff the content provider has | |
93 */ | |
94 private /+volatile+/ IFilter filter; | |
95 | |
96 /** | |
97 * Queued changes | |
98 */ | |
99 private ChangeQueue changeQueue; | |
100 | |
101 /** | |
102 * Listener that gets callbacks from the model | |
103 */ | |
104 private IConcurrentModelListener listener; | |
105 private void init_listener(){ | |
106 listener = new class IConcurrentModelListener { | |
107 /* (non-Javadoc) | |
108 * @see dwtx.jface.viewers.deferred.IConcurrentModelListener#add(java.lang.Object[]) | |
109 */ | |
110 public void add(Object[] added) { | |
111 this.outer.add(added); | |
112 } | |
113 | |
114 /* (non-Javadoc) | |
115 * @see dwtx.jface.viewers.deferred.IConcurrentModelListener#remove(java.lang.Object[]) | |
116 */ | |
117 public void remove(Object[] removed) { | |
118 this.outer.remove(removed); | |
119 } | |
120 | |
121 /* (non-Javadoc) | |
122 * @see dwtx.jface.viewers.deferred.IConcurrentModelListener#setContents(java.lang.Object[]) | |
123 */ | |
124 public void setContents(Object[] newContents) { | |
125 this.outer.setContents(newContents); | |
126 } | |
127 | |
128 /* (non-Javadoc) | |
129 * @see dwtx.jface.viewers.deferred.IConcurrentModelListener#update(java.lang.Object[]) | |
130 */ | |
131 public void update(Object[] changed) { | |
132 this.outer.update(changed); | |
133 } | |
134 | |
135 }; | |
136 } | |
137 /** | |
138 * Object that posts updates to the UI thread. Must synchronize on | |
139 * sortMutex when accessing. | |
140 */ | |
141 private ConcurrentTableUpdator updator; | |
142 | |
143 private IProgressMonitor sortingProgressMonitor; | |
144 private Thread sortThread = null; | |
145 | |
146 private /+volatile+/ FastProgressReporter sortMon; | |
147 | |
148 private /+volatile+/ ConcurrentTableUpdator.Range range; | |
149 | |
150 /** | |
151 * Creates a new background content provider | |
152 * | |
153 * @param table table that will receive updates | |
154 * @param model data source | |
155 * @param sortOrder initial sort order | |
156 */ | |
157 public this(AbstractVirtualTable table, | |
158 IConcurrentModel model, Comparator sortOrder) { | |
159 lock = new Object(); | |
160 filter = AcceptAllFilter.getInstance(); | |
161 changeQueue = new ChangeQueue(); | |
162 init_listener(); | |
163 sortingProgressMonitor = new NullProgressMonitor(); | |
164 sortMon = new FastProgressReporter(); | |
165 updator = new ConcurrentTableUpdator(table); | |
166 range = new ConcurrentTableUpdator.Range(0,0); | |
167 this.model = model; | |
168 this.sortOrder = sortOrder; | |
169 model.addListener(listener); | |
170 } | |
171 | |
172 /** | |
173 * Cleans up this content provider, detaches listeners, frees up memory, etc. | |
174 * Must be the last public method called on this object. | |
175 */ | |
176 public void dispose() { | |
177 cancelSortJob(); | |
178 updator.dispose(); | |
179 model.removeListener(listener); | |
180 } | |
181 | |
182 /** | |
183 * Force a refresh. Asks the model to re-send its complete contents. | |
184 */ | |
185 public void refresh() { | |
186 if (updator.isDisposed()) { | |
187 return; | |
188 } | |
189 model.requestUpdate(listener); | |
190 } | |
191 | |
192 /** | |
193 * Called from sortJob. Sorts the elements defined by sortStart and sortLength. | |
194 * Schedules a UI update when finished. | |
195 * | |
196 * @param mon monitor where progress will be reported | |
197 */ | |
198 private void doSort(IProgressMonitor mon) { | |
199 | |
200 // Workaround for some weirdness in the Jobs framework: if you cancel a monitor | |
201 // for a job that has ended and reschedule that same job, it will start | |
202 // the job with a monitor that is already cancelled. We can workaround this by | |
203 // removing all references to the progress monitor whenever the job terminates, | |
204 // but this would require additional synchronize blocks (which are slow) and more | |
205 // complexity. Instead, we just un-cancel the monitor at the start of each job. | |
206 mon.setCanceled(false); | |
207 | |
208 mon.beginTask(SORTING, 100); | |
209 | |
210 // Create a LazySortedCollection | |
211 Comparator order = sortOrder; | |
212 IFilter f = filter; | |
213 LazySortedCollection collection = new LazySortedCollection(order); | |
214 | |
215 // Fill it in with all existing known objects | |
216 Object[] knownObjects = updator.getKnownObjects(); | |
217 for (int i = 0; i < knownObjects.length; i++) { | |
218 Object object = knownObjects[i]; | |
219 if (object !is null) { | |
220 collection.add(object); | |
221 } | |
222 } | |
223 | |
224 bool dirty = false; | |
225 int prevSize = knownObjects.length; | |
226 updator.setTotalItems(prevSize); | |
227 | |
228 // Start processing changes | |
229 while(true) { | |
230 // If the sort order has changed, build a new LazySortedCollection with | |
231 // the new comparator | |
232 if (order !is sortOrder) { | |
233 dirty = true; | |
234 order = sortOrder; | |
235 // Copy all elements from the old collection to the new one | |
236 LazySortedCollection newCollection = new LazySortedCollection(order); | |
237 | |
238 Object[] items = collection.getItems(false); | |
239 for (int j = 0; j < items.length && order is sortOrder; j++) { | |
240 Object item = items[j]; | |
241 | |
242 newCollection.add(item); | |
243 } | |
244 | |
245 // If the sort order changed again, re-loop | |
246 if (order !is sortOrder) { | |
247 continue; | |
248 } | |
249 collection = newCollection; | |
250 continue; | |
251 } | |
252 | |
253 // If the filter has changed | |
254 if (f !is filter) { | |
255 dirty = true; | |
256 f = filter; | |
257 | |
258 Object[] items = collection.getItems(false); | |
259 | |
260 // Remove any items that don't pass the new filter | |
261 for (int j = 0; j < items.length && f is filter; j++) { | |
262 Object toTest = items[j]; | |
263 | |
264 if (!f.select(toTest)) { | |
265 collection.remove(toTest); | |
266 } | |
267 } | |
268 continue; | |
269 } | |
270 | |
271 // If there are pending changes, process one of them | |
272 if (!changeQueue.isEmpty()) { | |
273 dirty = true; | |
274 ChangeQueue.Change next = changeQueue.dequeue(); | |
275 | |
276 switch(next.getType()) { | |
277 case ChangeQueue.ADD: { | |
278 filteredAdd(collection, next.getElements(), f); | |
279 break; | |
280 } | |
281 case ChangeQueue.REMOVE: { | |
282 Object[] toRemove = next.getElements(); | |
283 | |
284 flush(toRemove, collection); | |
285 collection.removeAll(toRemove); | |
286 | |
287 break; | |
288 } | |
289 case ChangeQueue.UPDATE: { | |
290 Object[] items = next.getElements(); | |
291 | |
292 for (int i = 0; i < items.length; i++) { | |
293 Object item = items[i]; | |
294 | |
295 if (collection.contains(item)) { | |
296 // TODO: write a collection.update(...) method | |
297 collection.remove(item); | |
298 collection.add(item); | |
299 updator.clear(item); | |
300 } | |
301 } | |
302 | |
303 break; | |
304 } | |
305 case ChangeQueue.SET: { | |
306 Object[] items = next.getElements(); | |
307 collection.clear(); | |
308 filteredAdd(collection, items, f); | |
309 | |
310 break; | |
311 } | |
312 } | |
313 | |
314 continue; | |
315 } | |
316 | |
317 int totalElements = collection.size(); | |
318 if (limit !is -1) { | |
319 if (totalElements > limit) { | |
320 totalElements = limit; | |
321 } | |
322 } | |
323 | |
324 if (totalElements !is prevSize) { | |
325 prevSize = totalElements; | |
326 // Send the total items to the updator ASAP -- the user may want | |
327 // to scroll to a different section of the table, which would | |
328 // cause our sort range to change and cause this job to get cancelled. | |
329 updator.setTotalItems(totalElements); | |
330 dirty = true; | |
331 } | |
332 | |
333 // Terminate loop | |
334 if (!dirty) { | |
335 break; | |
336 } | |
337 | |
338 try { | |
339 ConcurrentTableUpdator.Range updateRange = updator.getVisibleRange(); | |
340 sortMon = new FastProgressReporter(); | |
341 range = updateRange; | |
342 int sortStart = updateRange.start; | |
343 int sortLength = updateRange.length; | |
344 | |
345 if (limit !is -1) { | |
346 collection.retainFirst(limit, sortMon); | |
347 } | |
348 | |
349 sortLength = Math.min(sortLength, totalElements - sortStart); | |
350 sortLength = Math.max(sortLength, 0); | |
351 | |
352 Object[] objectsOfInterest = new Object[sortLength]; | |
353 | |
354 collection.getRange(objectsOfInterest, sortStart, true, sortMon); | |
355 | |
356 // Send the new elements to the table | |
357 for (int i = 0; i < sortLength; i++) { | |
358 Object object = objectsOfInterest[i]; | |
359 updator.replace(object, sortStart + i); | |
360 } | |
361 | |
362 objectsOfInterest = new Object[collection.size()]; | |
363 | |
364 collection.getFirst(objectsOfInterest, true, sortMon); | |
365 | |
366 // Send the new elements to the table | |
367 for (int i = 0; i < totalElements; i++) { | |
368 Object object = objectsOfInterest[i]; | |
369 updator.replace(object, i); | |
370 } | |
371 | |
372 } catch (InterruptedException e) { | |
373 continue; | |
374 } | |
375 | |
376 dirty = false; | |
377 } | |
378 | |
379 mon.done(); | |
380 } | |
381 | |
382 /** | |
383 * @param collection | |
384 * @param toAdd | |
385 */ | |
386 private static void filteredAdd(LazySortedCollection collection, Object[] toAdd, IFilter filter) { | |
387 if (filter !is AcceptAllFilter.getInstance()) { | |
388 for (int i = 0; i < toAdd.length; i++) { | |
389 Object object = toAdd[i]; | |
390 | |
391 if (filter.select(object)) { | |
392 collection.add(object); | |
393 } | |
394 } | |
395 } else { | |
396 collection.addAll(toAdd); | |
397 } | |
398 } | |
399 | |
400 /** | |
401 * Sets the sort order for this content provider | |
402 * | |
403 * @param sorter sort order | |
404 */ | |
405 public void setSortOrder(Comparator sorter) { | |
406 Assert.isNotNull(cast(Object)sorter); | |
407 this.sortOrder = sorter; | |
408 sortMon.cancel(); | |
409 refresh(); | |
410 } | |
411 | |
412 /** | |
413 * Sets the filter for this content provider | |
414 * | |
415 * @param toSet filter to set | |
416 */ | |
417 public void setFilter(IFilter toSet) { | |
418 Assert.isNotNull(cast(Object)toSet); | |
419 this.filter = toSet; | |
420 sortMon.cancel(); | |
421 refresh(); | |
422 } | |
423 | |
424 /** | |
425 * Sets the maximum table size. Based on the current sort order, | |
426 * the table will be truncated if it grows beyond this size. | |
427 * Using a limit improves memory usage and performance, and is | |
428 * strongly recommended for large tables. | |
429 * | |
430 * @param limit maximum rows to show in the table or -1 if unbounded | |
431 */ | |
432 public void setLimit(int limit) { | |
433 this.limit = limit; | |
434 refresh(); | |
435 } | |
436 | |
437 /** | |
438 * Returns the maximum table size or -1 if unbounded | |
439 * | |
440 * @return the maximum number of rows in the table or -1 if unbounded | |
441 */ | |
442 public int getLimit() { | |
443 return limit; | |
444 } | |
445 | |
446 /** | |
447 * Checks if currently visible range has changed, and triggers and update | |
448 * and resort if necessary. Must be called in the UI thread, typically | |
449 * within a DWT.SetData callback. | |
450 * @param includeIndex the index that should be included in the visible range. | |
451 */ | |
452 public void checkVisibleRange(int includeIndex) { | |
453 updator.checkVisibleRange(includeIndex); | |
454 ConcurrentTableUpdator.Range newRange = updator.getVisibleRange(); | |
455 ConcurrentTableUpdator.Range oldRange = range; | |
456 | |
457 // If we're in the middle of processing an invalid range, cancel the sort | |
458 if (newRange.start !is oldRange.start || newRange.length !is oldRange.length) { | |
459 sortMon.cancel(); | |
460 } | |
461 } | |
462 | |
463 /** | |
464 * This lock protects the two bool variables sortThreadStarted and resortScheduled. | |
465 */ | |
466 private Object lock; | |
467 | |
468 /** | |
469 * true if the sort thread is running | |
470 */ | |
471 private bool sortThreadStarted = false; | |
472 | |
473 /** | |
474 * true if we need to sort | |
475 */ | |
476 private bool sortScheduled = false; | |
477 | |
478 private final class SortThread : Thread { | |
479 private this(String name) { | |
480 super(/+name+/); | |
481 } | |
482 | |
483 public void run() { | |
484 loop: while (true) { | |
485 synchronized (lock) { | |
486 sortScheduled = false; | |
487 } | |
488 try { | |
489 // this is the main work | |
490 doSort(sortingProgressMonitor); | |
491 } catch (Exception ex) { | |
492 // ignore | |
493 } | |
494 synchronized (lock) { | |
495 if (sortScheduled) { | |
496 continue loop; | |
497 } | |
498 sortThreadStarted = false; | |
499 break loop; | |
500 } | |
501 } | |
502 } | |
503 } | |
504 | |
505 /** | |
506 * Must be called whenever the model changes. Dirties this object and triggers a sort | |
507 * if necessary. | |
508 */ | |
509 private void makeDirty() { | |
510 synchronized (lock) { | |
511 sortMon.cancel(); | |
512 // request sorting | |
513 sortScheduled = true; | |
514 if (!sortThreadStarted) { | |
515 sortThreadStarted = true; | |
516 sortThread = new SortThread(SORTING); | |
517 sortThread.isDaemon = true; | |
518 sortThread.priority = sortThread.priority - 1; | |
519 sortThread.start(); | |
520 } | |
521 } | |
522 } | |
523 | |
524 /** | |
525 * Cancels any sort in progress. Note that we try to use the | |
526 * FastProgresReporter if possible since this is more responsive than | |
527 * cancelling the sort job. However, it is not a problem to cancel in both | |
528 * ways. | |
529 */ | |
530 private void cancelSortJob() { | |
531 sortMon.cancel(); | |
532 sortingProgressMonitor.setCanceled(true); | |
533 } | |
534 | |
535 /** | |
536 * Called when new elements are added to the model. | |
537 * | |
538 * @param toAdd | |
539 * newly added elements | |
540 */ | |
541 private void add(Object[] toAdd) { | |
542 changeQueue.enqueue(ChangeQueue.ADD, toAdd); | |
543 makeDirty(); | |
544 } | |
545 | |
546 /** | |
547 * Called with the complete contents of the model | |
548 * | |
549 * @param contents new contents of the model | |
550 */ | |
551 private void setContents(Object[] contents) { | |
552 changeQueue.enqueue(ChangeQueue.SET, contents); | |
553 makeDirty(); | |
554 } | |
555 | |
556 /** | |
557 * Called when elements are removed from the model | |
558 * | |
559 * @param toRemove elements removed from the model | |
560 */ | |
561 private void remove(Object[] toRemove) { | |
562 changeQueue.enqueue(ChangeQueue.REMOVE, toRemove); | |
563 makeDirty(); | |
564 refresh(); | |
565 } | |
566 | |
567 /** | |
568 * Notifies the updator that the given elements have changed | |
569 * | |
570 * @param toFlush changed elements | |
571 * @param collection collection of currently-known elements | |
572 */ | |
573 private void flush(Object[] toFlush, LazySortedCollection collection) { | |
574 for (int i = 0; i < toFlush.length; i++) { | |
575 Object item = toFlush[i]; | |
576 | |
577 if (collection.contains(item)) { | |
578 updator.clear(item); | |
579 } | |
580 } | |
581 } | |
582 | |
583 | |
584 /** | |
585 * Called when elements in the model change | |
586 * | |
587 * @param items changed items | |
588 */ | |
589 private void update(Object[] items) { | |
590 changeQueue.enqueue(ChangeQueue.UPDATE, items); | |
591 makeDirty(); | |
592 } | |
593 } |