1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Abstract container class that handles common functionality for
17 in-memory containers."""
18
19 from muntjac.data.util.default_item_sorter import DefaultItemSorter
20
21 from muntjac.data.container import \
22 IIndexed, IItemSetChangeNotifier, ISortable
23
24 from muntjac.data.util.abstract_container import AbstractContainer
25
26
29 """Abstract L{IContainer} class that handles common functionality for
30 in-memory containers. Concrete in-memory container classes can either
31 inherit this class, inherit L{AbstractContainer}, or implement the
32 L{IContainer} interface directly.
33
34 Adding and removing items (if desired) must be implemented in subclasses by
35 overriding the appropriate add*Item() and remove*Item() and removeAllItems()
36 methods, calling the corresponding L{internalAddItemAfter},
37 L{internalAddItemAt}, L{internalAddItemAtEnd}, L{internalRemoveItem} and
38 L{internalRemoveAllItems} methods.
39
40 By default, adding and removing container properties is not supported, and
41 subclasses need to implement L{getContainerPropertyIds}. Optionally,
42 subclasses can override L{addContainerProperty} and
43 L{removeContainerProperty} to implement them.
44
45 Features:
46 - L{IOrdered}
47 - L{IIndexed}
48 - L{Filterable} and L{SimpleFilterable} (internal implementation,
49 does not implement the interface directly)
50 - L{ISortable} (internal implementation, does not implement the
51 interface directly)
52
53 To implement L{ISortable}, subclasses need to implement
54 L{getSortablePropertyIds} and call the superclass method
55 L{sortContainer} in the method C{sort(object[], bool[])}.
56
57 To implement L{IFilterable}, subclasses need to implement the methods
58 L{Filterable.addContainerFilter} (calling L{addFilter}),
59 L{Filterable.removeAllContainerFilters} (calling L{removeAllFilters}) and
60 L{Filterable.removeContainerFilter} (calling L{removeFilter}).
61
62 To implement L{ISimpleFilterable}, subclasses also need to implement the
63 methods L{SimpleFilterable.addContainerFilter}
64 and L{SimpleFilterable.removeContainerFilters} calling L{addFilter} and
65 L{removeFilters} respectively.
66
67 <ITEMIDTYPE>:
68 the class of item identifiers in the container, use object if can
69 be any class
70
71 <PROPERTYIDCLASS>:
72 the class of property identifiers for the items in the container,
73 use Object if can be any class
74
75 <ITEMCLASS>:
76 the (base) class of the Item instances in the container, use
77 L{Item} if unknown
78 """
79
81 """Constructor for an abstract in-memory container."""
82 super(AbstractInMemoryContainer, self).__init__()
83
84
85
86
87
88 self._allItemIds = None
89
90
91
92
93
94
95
96
97 self._filteredItemIds = None
98
99
100
101 self._filters = set()
102
103
104 self._itemSorter = DefaultItemSorter()
105
106
107
108 self.setAllItemIds(list())
109
110
116
117
119 """Get an item even if filtered out.
120
121 For internal use only.
122 """
123 pass
124
125
126
127
128
129
130
131
134
137
138
140
141 if itemId is None:
142 return False
143 else:
144 return itemId in self.getVisibleItemIds()
145
146
149
150
158
159
167
168
170 if len(self) > 0:
171 return self.getIdByIndex(0)
172 else:
173 return None
174
175
177 if len(self) > 0:
178 return self.getIdByIndex(len(self) - 1)
179 else:
180 return None
181
182
184 if itemId is None:
185 return False
186
187 return itemId == self.firstItemId()
188
189
191 if itemId is None:
192 return False
193
194 return itemId == self.lastItemId()
195
196
199
200
206
207
209 raise NotImplementedError('Adding items not supported. Override the '
210 'relevant addItem*() methods if required as specified in '
211 'AbstractInMemoryContainer docstring.')
212
213
215 raise NotImplementedError('Adding items not supported. Override the '
216 'relevant addItem*() methods if required as specified in '
217 'AbstractInMemoryContainer docstring.')
218
219
221 raise NotImplementedError('Adding items not supported. Override the '
222 'relevant addItem*() methods if required as specified in '
223 'AbstractInMemoryContainer docstring.')
224
225
227 raise NotImplementedError('Removing items not supported. Override '
228 'the removeItem() method if required as specified in '
229 'AbstractInMemoryContainer docstring.')
230
231
233 raise NotImplementedError('Removing items not supported. Override '
234 'the removeAllItems() method if required as specified in '
235 'AbstractInMemoryContainer docstring.')
236
237
239 raise NotImplementedError('Adding container properties not '
240 'supported. Override the addContainerProperty() method '
241 'if required.')
242
243
245 raise NotImplementedError('Removing container properties not '
246 'supported. Override the addContainerProperty() method '
247 'if required.')
248
249
252
253
254 - def addCallback(self, callback, eventType=None, *args):
257
258
261
262
266
267
269 """Filter the view to recreate the visible item list from the
270 unfiltered items, and send a notification if the set of visible
271 items changed in any way.
272 """
273 if self.doFilterContainer(len(self.getFilters()) > 0):
274 self.fireItemSetChange()
275
276
278 """Filters the data in the container and updates internal data
279 structures. This method should reset any internal data structures
280 and then repopulate them so L{getItemIds} and other methods only
281 return the filtered items.
282
283 @param hasFilters:
284 true if filters has been set for the container, false
285 otherwise
286 @return: true if the item set has changed as a result of the filtering
287 """
288 if not hasFilters:
289 changed = (len(self.getAllItemIds())
290 != len(self.getVisibleItemIds()))
291 self.setFilteredItemIds(None)
292 return changed
293
294
295 originalFilteredItemIds = self.getFilteredItemIds()
296 wasUnfiltered = False
297 if originalFilteredItemIds is None:
298 originalFilteredItemIds = list()
299 wasUnfiltered = True
300
301 self.setFilteredItemIds(list())
302
303
304 equal = True
305 origIt = iter(originalFilteredItemIds)
306 for idd in self.getAllItemIds():
307 if self.passesFilters(idd):
308
309 try:
310 equal = equal and (origIt.next() == idd)
311 except StopIteration:
312 equal = False
313
314 self.getFilteredItemIds().append(idd)
315
316 try:
317 origIt.next()
318 return True
319 except StopIteration:
320 return (wasUnfiltered and (len(self.getAllItemIds()) > 0)
321 or (not equal))
322
323
325 """Checks if the given itemId passes the filters set for the container.
326 The caller should make sure the itemId exists in the container. For
327 non-existing itemIds the behaviour is undefined.
328
329 @param itemId:
330 An itemId that exists in the container.
331 @return: true if the itemId passes all filters or no filters are set,
332 false otherwise.
333 """
334 item = self.getUnfilteredItem(itemId)
335 if len(self.getFilters()) == 0:
336 return True
337
338 for f in self.getFilters():
339 if not f.passesFilter(itemId, item):
340 return False
341
342 return True
343
344
346 """Adds a container filter and re-filter the view.
347
348 The filter must implement Filter and its sub-filters (if any) must also
349 be in-memory filterable.
350
351 This can be used to implement L{Filterable.addContainerFilter} and
352 optionally also L{SimpleFilterable.addContainerFilter}
353 (with L{SimpleStringFilter}).
354
355 Note that in some cases, incompatible filters cannot be detected when
356 added and an L{UnsupportedFilterException} may occur when performing
357 filtering.
358
359 @raise UnsupportedFilterException:
360 if the filter is detected as not supported by the container
361 """
362 self.getFilters().add(fltr)
363 self.filterAll()
364
365
367 """Remove a specific container filter and re-filter the view
368 (if necessary).
369
370 This can be used to implement L{Filterable.removeContainerFilter}.
371 """
372 for f in self.getFilters().copy():
373 if f == fltr:
374 self.getFilters().remove(f)
375 self.filterAll()
376 return
377
378
380 """Remove all container filters for all properties and re-filter
381 the view.
382
383 This can be used to implement L{Filterable.removeAllContainerFilters}.
384 """
385 if len(self.getFilters()) == 0:
386 return
387
388 self.getFilters().clear()
389 self.filterAll()
390
391
393 """Checks if there is a filter that applies to a given property.
394
395 @return: true if there is an active filter for the property
396 """
397 if len(self.getFilters()) == 0 or (propertyId is None):
398 return False
399
400 for f in self.getFilters():
401 if f.appliesToProperty(propertyId):
402 return True
403
404 return False
405
406
408 """Remove all container filters for a given property identifier and
409 re-filter the view. This also removes filters applying to multiple
410 properties including the one identified by propertyId.
411
412 This can be used to implement L{Filterable.removeContainerFilters}.
413
414 @return: Collection<Filter> removed filters
415 """
416 if len(self.getFilters()) == 0 or (propertyId is None):
417 return list()
418
419 removedFilters = list()
420 for f in set( self.getFilters() ):
421 if f.appliesToProperty(propertyId):
422 removedFilters.append(f)
423 self.getFilters().remove(f)
424
425 if len(removedFilters) > 0:
426 self.filterAll()
427 return removedFilters
428
429 return list()
430
431
433 """Returns the ItemSorter used for comparing items in a sort. See
434 L{setItemSorter} for more information.
435
436 @return: The ItemSorter used for comparing two items in a sort.
437 """
438 return self._itemSorter
439
440
442 """Sets the ItemSorter used for comparing items in a sort. The
443 L{ItemSorter.compare} method is called with item ids to perform
444 the sorting. A default ItemSorter is used if this is not explicitly
445 set.
446
447 @param itemSorter:
448 The ItemSorter used for comparing two items in a sort (not
449 null).
450 """
451 self._itemSorter = itemSorter
452
453
455 """Sort base implementation to be used to implement L{ISortable}.
456
457 Subclasses should call this from a public L{sort} method when
458 implementing ISortable.
459
460 @see: L{ISortable.sort}
461 """
462 if not isinstance(self, ISortable):
463 raise NotImplementedError('Cannot sort a IContainer '
464 'that does not implement ISortable')
465
466
467 self.getItemSorter().setSortProperties(self, propertyId, ascending)
468
469
470 self.doSort()
471
472
473 if self.isFiltered():
474 self.filterAll()
475 else:
476 self.fireItemSetChange()
477
478
480 """Perform the sorting of the data structures in the container. This
481 is invoked when the C{itemSorter} has been prepared for the sort
482 operation. Typically this method calls L{sorted} on all arrays
483 (containing item ids) that need to be sorted.
484 """
485 sortedIds = sorted(self.getAllItemIds(), cmp=self.getItemSorter())
486 self.setAllItemIds(sortedIds)
487
488
490 """Returns the sortable property identifiers for the container. Can
491 be used to implement L{ISortable.getSortableContainerPropertyIds}.
492 """
493 sortables = list()
494
495 for propertyId in self.getContainerPropertyIds():
496 propertyType = self.getType(propertyId)
497 if (hasattr(propertyType, '__eq__')
498 or isinstance(propertyType, (int, long, float, bool))):
499 sortables.append(propertyId)
500
501 return sortables
502
503
505 """Removes all items from the internal data structures of this class.
506 This can be used to implement L{removeAllItems} in subclasses.
507
508 No notification is sent, the caller has to fire a suitable item set
509 change notification.
510 """
511
512 del self.getAllItemIds()[:]
513 if self.isFiltered():
514 del self.getFilteredItemIds()[:]
515
516
518 """Removes a single item from the internal data structures of this
519 class. This can be used to implement L{removeItem} in subclasses.
520
521 No notification is sent, the caller has to fire a suitable item set
522 change notification.
523
524 @param itemId:
525 the identifier of the item to remove
526 @return: true if an item was successfully removed, false if failed to
527 remove or no such item
528 """
529 if itemId is None:
530 return False
531
532 try:
533 self.getAllItemIds().remove(itemId)
534 result = True
535 except ValueError:
536 result = False
537
538 if result and self.isFiltered():
539 try:
540 self.getFilteredItemIds().remove(itemId)
541 except ValueError:
542 pass
543
544 return result
545
546
548 """Adds the bean to all internal data structures at the given position.
549 Fails if an item with itemId is already in the container. Returns a the
550 item if it was added successfully, null otherwise.
551
552 Caller should initiate filtering after calling this method.
553
554 For internal use only - subclasses should use L{internalAddItemAtEnd},
555 L{internalAddItemAt} and L{internalAddItemAfter} instead.
556
557 @param position:
558 The position at which the item should be inserted in the
559 unfiltered collection of items
560 @param itemId:
561 The item identifier for the item to insert
562 @param item:
563 The item to insert
564
565 @return: ITEMCLASS if the item was added successfully, null otherwise
566 """
567 if (position < 0 or position > len(self.getAllItemIds())
568 or itemId is None or item is None):
569 return None
570
571
572 if itemId in self.getAllItemIds():
573 return None
574
575
576
577 self.getAllItemIds().insert(position, itemId)
578 self.registerNewItem(position, itemId, item)
579
580 return item
581
582
584 """Add an item at the end of the container, and perform filtering if
585 necessary. An event is fired if the filtered view changes.
586
587 @param newItemId:
588 @param item:
589 new item to add
590 @param fltr:
591 true to perform filtering and send event after adding the
592 item, false to skip these operations for batch inserts - if
593 false, caller needs to make sure these operations are
594 performed at the end of the batch
595 @return: item added or null if no item was added
596 """
597 newItem = self.internalAddAt(len(self.getAllItemIds()), newItemId, item)
598
599 if newItem is not None and fltr:
600
601 self.filterAll()
602 if not self.isFiltered():
603
604 self.fireItemAdded(self.indexOfId(newItemId), newItemId, item)
605
606 return newItem
607
608
610 """Add an item after a given (visible) item, and perform filtering.
611 An event is fired if the filtered view changes.
612
613 The new item is added at the beginning if previousItemId is null.
614
615 @param previousItemId:
616 item id of a visible item after which to add the new item, or
617 null to add at the beginning
618 @param newItemId:
619 @param item:
620 new item to add
621 @param fltr:
622 true to perform filtering and send event after adding the
623 item, false to skip these operations for batch inserts - if
624 false, caller needs to make sure these operations are
625 performed at the end of the batch
626 @return: item added or null if no item was added
627 """
628
629 newItem = None
630 if previousItemId is None:
631 newItem = self.internalAddAt(0, newItemId, item)
632 elif self.containsId(previousItemId):
633 try:
634 idx = self.getAllItemIds().index(previousItemId)
635 except ValueError:
636 idx = -1
637 newItem = self.internalAddAt(idx + 1, newItemId, item)
638
639 if newItem is not None and fltr:
640
641 self.filterAll()
642 if not self.isFiltered():
643
644 self.fireItemAdded(self.indexOfId(newItemId), newItemId, item)
645
646 return newItem
647
648
650 """Add an item at a given (visible after filtering) item index, and
651 perform filtering. An event is fired if the filtered view changes.
652
653 @param index:
654 position where to add the item (visible/view index)
655 @param newItemId:
656 @param item:
657 new item to add
658 @param fltr:
659 true to perform filtering and send event after adding the
660 item, false to skip these operations for batch inserts - if
661 false, caller needs to make sure these operations are
662 performed at the end of the batch
663 @return: item added or null if no item was added
664 """
665 if (index < 0) or (index > len(self)):
666 return None
667 elif index == 0:
668
669 return self.internalAddItemAfter(None, newItemId, item, fltr)
670 else:
671
672 return self.internalAddItemAfter(self.getIdByIndex(index - 1),
673 newItemId, item, fltr)
674
675
677 """Registers a new item as having been added to the container. This
678 can involve storing the item or any relevant information about it in
679 internal container-specific collections if necessary, as well as
680 registering listeners etc.
681
682 The full identifier list in L{AbstractInMemoryContainer} has already
683 been updated to reflect the new item when this method is called.
684 """
685 pass
686
687
689 """Notify item set change listeners that an item has been added to the
690 container.
691
692 Unless subclasses specify otherwise, the default notification indicates
693 a full refresh.
694
695 @param position:
696 position of the added item in the view (if visible)
697 @param itemId:
698 id of the added item
699 @param item:
700 the added item
701 """
702 self.fireItemSetChange()
703
704
706 """Notify item set change listeners that an item has been removed from
707 the container.
708
709 Unless subclasses specify otherwise, the default notification indicates
710 a full refresh.
711
712 @param position:
713 position of the removed item in the view prior to removal
714 (if was visible)
715 @param itemId:
716 id of the removed item, of type L{object} to satisfy
717 L{IContainer.removeItem} API
718 """
719 self.fireItemSetChange()
720
721
723 """Returns the internal list of visible item identifiers after
724 filtering.
725
726 For internal use only.
727 """
728 if self.isFiltered():
729 return self.getFilteredItemIds()
730 else:
731 return self.getAllItemIds()
732
733
735 """Returns true is the container has active filters.
736
737 @return: true if the container is currently filtered
738 """
739 return self._filteredItemIds is not None
740
741
743 """Internal helper method to set the internal list of filtered item
744 identifiers. Should not be used outside this class except for
745 implementing clone(), may disappear from future versions.
746 """
747 self._filteredItemIds = filteredItemIds
748
749
751 """Internal helper method to get the internal list of filtered item
752 identifiers. Should not be used outside this class except for
753 implementing clone(), may disappear from future versions - use
754 L{getVisibleItemIds} in other contexts.
755
756 @return: List<ITEMIDTYPE>
757 """
758 return self._filteredItemIds
759
760
762 """Internal helper method to set the internal list of all item
763 identifiers. Should not be used outside this class except for
764 implementing clone(), may disappear from future versions.
765 """
766 self._allItemIds = allItemIds
767
768
770 """Internal helper method to get the internal list of all item
771 identifiers. Avoid using this method outside this class, may disappear
772 in future versions.
773
774 @return: List<ITEMIDTYPE>
775 """
776 return self._allItemIds
777
778
780 """Set the internal collection of filters without performing filtering.
781
782 This method is mostly for internal use, use L{addFilter} and
783 C{remove*Filter*} (which also re-filter the container) instead
784 when possible.
785 """
786 self._filters = filters
787
788
790 """Returns the internal collection of filters. The returned collection
791 should not be modified by callers outside this class.
792
793 @return: Set<Filter>
794 """
795 return self._filters
796