Mercurial > projects > dwt-addons
annotate dwtx/jface/viewers/deferred/BackgroundContentProvider.d @ 200:eb3414669eb0 default tip
fix for dmd 1.041 and tango 0.99.8
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 28 Mar 2009 03:09:57 +0100 |
parents | c3583c6ec027 |
children |
rev | line source |
---|---|
10 | 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; | |
167 | 32 import dwtx.dwtxhelper.JThread; |
10 | 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; | |
167 | 144 private JThread sortThread = null; |
10 | 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 } | |
192
c3583c6ec027
Added missing default cases for switch statements
Frank Benoit <benoit@tionex.de>
parents:
167
diff
changeset
|
312 default: |
10 | 313 } |
314 | |
315 continue; | |
316 } | |
317 | |
318 int totalElements = collection.size(); | |
319 if (limit !is -1) { | |
320 if (totalElements > limit) { | |
321 totalElements = limit; | |
322 } | |
323 } | |
324 | |
325 if (totalElements !is prevSize) { | |
326 prevSize = totalElements; | |
327 // Send the total items to the updator ASAP -- the user may want | |
328 // to scroll to a different section of the table, which would | |
329 // cause our sort range to change and cause this job to get cancelled. | |
330 updator.setTotalItems(totalElements); | |
331 dirty = true; | |
332 } | |
333 | |
334 // Terminate loop | |
335 if (!dirty) { | |
336 break; | |
337 } | |
338 | |
339 try { | |
340 ConcurrentTableUpdator.Range updateRange = updator.getVisibleRange(); | |
341 sortMon = new FastProgressReporter(); | |
342 range = updateRange; | |
343 int sortStart = updateRange.start; | |
344 int sortLength = updateRange.length; | |
345 | |
346 if (limit !is -1) { | |
347 collection.retainFirst(limit, sortMon); | |
348 } | |
349 | |
350 sortLength = Math.min(sortLength, totalElements - sortStart); | |
351 sortLength = Math.max(sortLength, 0); | |
352 | |
353 Object[] objectsOfInterest = new Object[sortLength]; | |
354 | |
355 collection.getRange(objectsOfInterest, sortStart, true, sortMon); | |
356 | |
357 // Send the new elements to the table | |
358 for (int i = 0; i < sortLength; i++) { | |
359 Object object = objectsOfInterest[i]; | |
360 updator.replace(object, sortStart + i); | |
361 } | |
362 | |
363 objectsOfInterest = new Object[collection.size()]; | |
364 | |
365 collection.getFirst(objectsOfInterest, true, sortMon); | |
366 | |
367 // Send the new elements to the table | |
368 for (int i = 0; i < totalElements; i++) { | |
369 Object object = objectsOfInterest[i]; | |
370 updator.replace(object, i); | |
371 } | |
372 | |
373 } catch (InterruptedException e) { | |
374 continue; | |
375 } | |
376 | |
377 dirty = false; | |
378 } | |
379 | |
380 mon.done(); | |
381 } | |
382 | |
383 /** | |
384 * @param collection | |
385 * @param toAdd | |
386 */ | |
387 private static void filteredAdd(LazySortedCollection collection, Object[] toAdd, IFilter filter) { | |
388 if (filter !is AcceptAllFilter.getInstance()) { | |
389 for (int i = 0; i < toAdd.length; i++) { | |
390 Object object = toAdd[i]; | |
391 | |
392 if (filter.select(object)) { | |
393 collection.add(object); | |
394 } | |
395 } | |
396 } else { | |
397 collection.addAll(toAdd); | |
398 } | |
399 } | |
400 | |
401 /** | |
402 * Sets the sort order for this content provider | |
403 * | |
404 * @param sorter sort order | |
405 */ | |
406 public void setSortOrder(Comparator sorter) { | |
407 Assert.isNotNull(cast(Object)sorter); | |
408 this.sortOrder = sorter; | |
409 sortMon.cancel(); | |
410 refresh(); | |
411 } | |
412 | |
413 /** | |
414 * Sets the filter for this content provider | |
415 * | |
416 * @param toSet filter to set | |
417 */ | |
418 public void setFilter(IFilter toSet) { | |
419 Assert.isNotNull(cast(Object)toSet); | |
420 this.filter = toSet; | |
421 sortMon.cancel(); | |
422 refresh(); | |
423 } | |
424 | |
425 /** | |
426 * Sets the maximum table size. Based on the current sort order, | |
427 * the table will be truncated if it grows beyond this size. | |
428 * Using a limit improves memory usage and performance, and is | |
429 * strongly recommended for large tables. | |
430 * | |
431 * @param limit maximum rows to show in the table or -1 if unbounded | |
432 */ | |
433 public void setLimit(int limit) { | |
434 this.limit = limit; | |
435 refresh(); | |
436 } | |
437 | |
438 /** | |
439 * Returns the maximum table size or -1 if unbounded | |
440 * | |
441 * @return the maximum number of rows in the table or -1 if unbounded | |
442 */ | |
443 public int getLimit() { | |
444 return limit; | |
445 } | |
446 | |
447 /** | |
448 * Checks if currently visible range has changed, and triggers and update | |
449 * and resort if necessary. Must be called in the UI thread, typically | |
450 * within a DWT.SetData callback. | |
451 * @param includeIndex the index that should be included in the visible range. | |
452 */ | |
453 public void checkVisibleRange(int includeIndex) { | |
454 updator.checkVisibleRange(includeIndex); | |
455 ConcurrentTableUpdator.Range newRange = updator.getVisibleRange(); | |
456 ConcurrentTableUpdator.Range oldRange = range; | |
457 | |
458 // If we're in the middle of processing an invalid range, cancel the sort | |
459 if (newRange.start !is oldRange.start || newRange.length !is oldRange.length) { | |
460 sortMon.cancel(); | |
461 } | |
462 } | |
463 | |
464 /** | |
465 * This lock protects the two bool variables sortThreadStarted and resortScheduled. | |
466 */ | |
467 private Object lock; | |
468 | |
469 /** | |
470 * true if the sort thread is running | |
471 */ | |
472 private bool sortThreadStarted = false; | |
473 | |
474 /** | |
475 * true if we need to sort | |
476 */ | |
477 private bool sortScheduled = false; | |
478 | |
167 | 479 private final class SortThread : JThread { |
10 | 480 private this(String name) { |
167 | 481 super(name); |
10 | 482 } |
483 | |
167 | 484 public override void run() { |
10 | 485 loop: while (true) { |
486 synchronized (lock) { | |
487 sortScheduled = false; | |
488 } | |
489 try { | |
490 // this is the main work | |
491 doSort(sortingProgressMonitor); | |
492 } catch (Exception ex) { | |
493 // ignore | |
494 } | |
495 synchronized (lock) { | |
496 if (sortScheduled) { | |
497 continue loop; | |
498 } | |
499 sortThreadStarted = false; | |
500 break loop; | |
501 } | |
502 } | |
503 } | |
504 } | |
505 | |
506 /** | |
507 * Must be called whenever the model changes. Dirties this object and triggers a sort | |
508 * if necessary. | |
509 */ | |
510 private void makeDirty() { | |
511 synchronized (lock) { | |
512 sortMon.cancel(); | |
513 // request sorting | |
514 sortScheduled = true; | |
515 if (!sortThreadStarted) { | |
516 sortThreadStarted = true; | |
517 sortThread = new SortThread(SORTING); | |
167 | 518 sortThread.setDaemon( true ); |
519 sortThread.setPriority( sortThread.getPriority() - 1 ); | |
10 | 520 sortThread.start(); |
521 } | |
522 } | |
523 } | |
524 | |
525 /** | |
526 * Cancels any sort in progress. Note that we try to use the | |
527 * FastProgresReporter if possible since this is more responsive than | |
528 * cancelling the sort job. However, it is not a problem to cancel in both | |
529 * ways. | |
530 */ | |
531 private void cancelSortJob() { | |
532 sortMon.cancel(); | |
533 sortingProgressMonitor.setCanceled(true); | |
534 } | |
535 | |
536 /** | |
537 * Called when new elements are added to the model. | |
538 * | |
539 * @param toAdd | |
540 * newly added elements | |
541 */ | |
542 private void add(Object[] toAdd) { | |
543 changeQueue.enqueue(ChangeQueue.ADD, toAdd); | |
544 makeDirty(); | |
545 } | |
546 | |
547 /** | |
548 * Called with the complete contents of the model | |
549 * | |
550 * @param contents new contents of the model | |
551 */ | |
552 private void setContents(Object[] contents) { | |
553 changeQueue.enqueue(ChangeQueue.SET, contents); | |
554 makeDirty(); | |
555 } | |
556 | |
557 /** | |
558 * Called when elements are removed from the model | |
559 * | |
560 * @param toRemove elements removed from the model | |
561 */ | |
562 private void remove(Object[] toRemove) { | |
563 changeQueue.enqueue(ChangeQueue.REMOVE, toRemove); | |
564 makeDirty(); | |
565 refresh(); | |
566 } | |
567 | |
568 /** | |
569 * Notifies the updator that the given elements have changed | |
570 * | |
571 * @param toFlush changed elements | |
572 * @param collection collection of currently-known elements | |
573 */ | |
574 private void flush(Object[] toFlush, LazySortedCollection collection) { | |
575 for (int i = 0; i < toFlush.length; i++) { | |
576 Object item = toFlush[i]; | |
577 | |
578 if (collection.contains(item)) { | |
579 updator.clear(item); | |
580 } | |
581 } | |
582 } | |
583 | |
584 | |
585 /** | |
586 * Called when elements in the model change | |
587 * | |
588 * @param items changed items | |
589 */ | |
590 private void update(Object[] items) { | |
591 changeQueue.enqueue(ChangeQueue.UPDATE, items); | |
592 makeDirty(); | |
593 } | |
594 } |