Package muntjac :: Package data :: Package util :: Module abstract_in_memory_container
[hide private]
[frames] | no frames]

Source Code for Module muntjac.data.util.abstract_in_memory_container

  1  # Copyright (C) 2012 Vaadin Ltd.  
  2  # Copyright (C) 2012 Richard Lincoln 
  3  #  
  4  # Licensed under the Apache License, Version 2.0 (the "License");  
  5  # you may not use this file except in compliance with the License.  
  6  # You may obtain a copy of the License at  
  7  #  
  8  #     http://www.apache.org/licenses/LICENSE-2.0  
  9  #  
 10  # Unless required by applicable law or agreed to in writing, software  
 11  # distributed under the License is distributed on an "AS IS" BASIS,  
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
 13  # See the License for the specific language governing permissions and  
 14  # limitations under the License. 
 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   
27 -class AbstractInMemoryContainer(AbstractContainer, IItemSetChangeNotifier, 28 IIndexed): #IContainer,
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
80 - def __init__(self):
81 """Constructor for an abstract in-memory container.""" 82 super(AbstractInMemoryContainer, self).__init__() 83 84 #: An ordered L{List} of all item identifiers in the container, 85 # including those that have been filtered out. 86 # 87 # Must not be null. 88 self._allItemIds = None 89 90 #: An ordered L{List} of item identifiers in the container after 91 # filtering, excluding those that have been filtered out. 92 # 93 # This is what the external API of the L{IContainer} interface and its 94 # subinterfaces shows (e.g. L{#size()}, L{#nextItemId(Object)}). 95 # 96 # If null, the full item id list is used instead. 97 self._filteredItemIds = None 98 99 #: Filters that are applied to the container to limit the items visible 100 # in it 101 self._filters = set() 102 103 #: The item sorter which is used for sorting the container. 104 self._itemSorter = DefaultItemSorter() 105 106 #: IContainer interface methods with more specific return class 107 # default implementation, can be overridden 108 self.setAllItemIds(list()) # FIXME: use ListSet
109 110
111 - def getItem(self, itemId):
112 if self.containsId(itemId): 113 return self.getUnfilteredItem(itemId) 114 else: 115 return None
116 117
118 - def getUnfilteredItem(self, itemId):
119 """Get an item even if filtered out. 120 121 For internal use only. 122 """ 123 pass
124 125 # cannot override getContainerPropertyIds() and getItemIds(): if subclass 126 # uses Object as ITEMIDCLASS or PROPERTYIDCLASS, Collection<Object> cannot 127 # be cast to Collection<MyInterface> 128 # public abstract Collection<PROPERTYIDCLASS> getContainerPropertyIds(); 129 # public abstract Collection<ITEMIDCLASS> getItemIds(); 130 # IContainer interface method implementations 131
132 - def size(self):
133 return len(self.getVisibleItemIds())
134
135 - def __len__(self):
136 return self.size()
137 138
139 - def containsId(self, itemId):
140 # only look at visible items after filtering 141 if itemId is None: 142 return False 143 else: 144 return itemId in self.getVisibleItemIds()
145 146
147 - def getItemIds(self):
148 return list(self.getVisibleItemIds())
149 150
151 - def nextItemId(self, itemId):
152 index = self.indexOfId(itemId) 153 if index >= 0 and index < len(self) - 1: 154 return self.getIdByIndex(index + 1) 155 else: 156 # out of bounds 157 return None
158 159
160 - def prevItemId(self, itemId):
161 index = self.indexOfId(itemId) 162 if index > 0: 163 return self.getIdByIndex(index - 1) 164 else: 165 # out of bounds 166 return None
167 168
169 - def firstItemId(self):
170 if len(self) > 0: 171 return self.getIdByIndex(0) 172 else: 173 return None
174 175
176 - def lastItemId(self):
177 if len(self) > 0: 178 return self.getIdByIndex(len(self) - 1) 179 else: 180 return None
181 182
183 - def isFirstId(self, itemId):
184 if itemId is None: 185 return False 186 187 return itemId == self.firstItemId()
188 189
190 - def isLastId(self, itemId):
191 if itemId is None: 192 return False 193 194 return itemId == self.lastItemId()
195 196
197 - def getIdByIndex(self, index):
198 return self.getVisibleItemIds()[index]
199 200
201 - def indexOfId(self, itemId):
202 try: 203 return self.getVisibleItemIds().index(itemId) 204 except ValueError: 205 return -1
206 207
208 - def addItemAt(self, index, newItemId=None):
209 raise NotImplementedError('Adding items not supported. Override the ' 210 'relevant addItem*() methods if required as specified in ' 211 'AbstractInMemoryContainer docstring.')
212 213
214 - def addItemAfter(self, previousItemId, newItemId=None):
215 raise NotImplementedError('Adding items not supported. Override the ' 216 'relevant addItem*() methods if required as specified in ' 217 'AbstractInMemoryContainer docstring.')
218 219
220 - def addItem(self, itemId=None):
221 raise NotImplementedError('Adding items not supported. Override the ' 222 'relevant addItem*() methods if required as specified in ' 223 'AbstractInMemoryContainer docstring.')
224 225
226 - def removeItem(self, itemId):
227 raise NotImplementedError('Removing items not supported. Override ' 228 'the removeItem() method if required as specified in ' 229 'AbstractInMemoryContainer docstring.')
230 231
232 - def removeAllItems(self):
233 raise NotImplementedError('Removing items not supported. Override ' 234 'the removeAllItems() method if required as specified in ' 235 'AbstractInMemoryContainer docstring.')
236 237
238 - def addContainerProperty(self, propertyId, typ, defaultValue):
239 raise NotImplementedError('Adding container properties not ' 240 'supported. Override the addContainerProperty() method ' 241 'if required.')
242 243
244 - def removeContainerProperty(self, propertyId):
245 raise NotImplementedError('Removing container properties not ' 246 'supported. Override the addContainerProperty() method ' 247 'if required.')
248 249
250 - def addListener(self, listener, iface=None):
251 super(AbstractInMemoryContainer, self).addListener(listener, iface)
252 253
254 - def addCallback(self, callback, eventType=None, *args):
255 super(AbstractInMemoryContainer, self).addCallback(callback, 256 eventType, *args)
257 258
259 - def removeListener(self, listener, iface=None):
260 super(AbstractInMemoryContainer, self).removeListener(listener, iface)
261 262
263 - def removeCallback(self, callback, eventType=None):
264 super(AbstractInMemoryContainer, self).removeCallback(callback, 265 eventType)
266 267
268 - def filterAll(self):
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
277 - def doFilterContainer(self, hasFilters):
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 # Reset filtered list 295 originalFilteredItemIds = self.getFilteredItemIds() 296 wasUnfiltered = False 297 if originalFilteredItemIds is None: 298 originalFilteredItemIds = list() 299 wasUnfiltered = True 300 301 self.setFilteredItemIds(list()) # FIXME: use ListSet 302 303 # Filter 304 equal = True 305 origIt = iter(originalFilteredItemIds) 306 for idd in self.getAllItemIds(): 307 if self.passesFilters(idd): 308 # filtered list comes from the full list, can use == 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
324 - def passesFilters(self, itemId):
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
345 - def addFilter(self, fltr):
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
366 - def removeFilter(self, fltr):
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
379 - def removeAllFilters(self):
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
392 - def isPropertyFiltered(self, propertyId):
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
407 - def removeFilters(self, propertyId):
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
432 - def getItemSorter(self):
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
441 - def setItemSorter(self, itemSorter):
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
454 - def sortContainer(self, propertyId, ascending):
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 # Set up the item sorter for the sort operation 467 self.getItemSorter().setSortProperties(self, propertyId, ascending) 468 469 # Perform the actual sort 470 self.doSort() 471 472 # Post sort updates 473 if self.isFiltered(): 474 self.filterAll() 475 else: 476 self.fireItemSetChange()
477 478
479 - def doSort(self):
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
489 - def getSortablePropertyIds(self):
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))): # FIXME: Comparable isPrimitive 499 sortables.append(propertyId) 500 501 return sortables
502 503
504 - def internalRemoveAllItems(self):
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 # Removes all Items 512 del self.getAllItemIds()[:] 513 if self.isFiltered(): 514 del self.getFilteredItemIds()[:]
515 516
517 - def internalRemoveItem(self, itemId):
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
547 - def internalAddAt(self, position, itemId, item):
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 # Make sure that the item has not been added previously 572 if itemId in self.getAllItemIds(): 573 return None 574 575 # "filteredList" will be updated in filterAll() which should be invoked 576 # by the caller after calling this method. 577 self.getAllItemIds().insert(position, itemId) 578 self.registerNewItem(position, itemId, item) 579 580 return item
581 582
583 - def internalAddItemAtEnd(self, newItemId, item, fltr):
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 # TODO filter only this item, use fireItemAdded() 601 self.filterAll() 602 if not self.isFiltered(): 603 # TODO hack: does not detect change in filterAll() in this case 604 self.fireItemAdded(self.indexOfId(newItemId), newItemId, item) 605 606 return newItem
607 608
609 - def internalAddItemAfter(self, previousItemId, newItemId, item, fltr):
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 # only add if the previous item is visible 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 # TODO filter only this item, use fireItemAdded() 641 self.filterAll() 642 if not self.isFiltered(): 643 # TODO hack: does not detect change in filterAll() in this case 644 self.fireItemAdded(self.indexOfId(newItemId), newItemId, item) 645 646 return newItem
647 648
649 - def internalAddItemAt(self, index, newItemId, item, fltr):
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 # add before any item, visible or not 669 return self.internalAddItemAfter(None, newItemId, item, fltr) 670 else: 671 # if index==size(), adds immediately after last visible item 672 return self.internalAddItemAfter(self.getIdByIndex(index - 1), 673 newItemId, item, fltr)
674 675
676 - def registerNewItem(self, position, itemId, item):
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
688 - def fireItemAdded(self, position, itemId, item):
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
705 - def fireItemRemoved(self, position, itemId):
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
722 - def getVisibleItemIds(self):
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
734 - def isFiltered(self):
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
742 - def setFilteredItemIds(self, filteredItemIds):
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
750 - def getFilteredItemIds(self):
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
761 - def setAllItemIds(self, allItemIds):
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
769 - def getAllItemIds(self):
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
779 - def setFilters(self, filters):
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
789 - def getFilters(self):
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