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

Source Code for Module muntjac.data.util.container_hierarchical_wrapper

  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  """A wrapper class for adding external hierarchy to containers not 
 17  implementing the IHierarchical interface.""" 
 18   
 19  from muntjac.util import OrderedSet 
 20   
 21  from muntjac.data.container import \ 
 22      (IContainer, IHierarchical, IItemSetChangeListener, 
 23       IItemSetChangeNotifier, IPropertySetChangeListener, 
 24       IPropertySetChangeNotifier, IItemSetChangeEvent, 
 25       IPropertySetChangeEvent) 
 26   
 27  from muntjac.data.util.hierarchical_container import HierarchicalContainer 
 28   
 29   
30 -class ContainerHierarchicalWrapper(IHierarchical, IContainer, 31 IItemSetChangeNotifier, IPropertySetChangeNotifier):
32 """A wrapper class for adding external hierarchy to containers not 33 implementing the L{IHierarchical} interface. 34 35 If the wrapped container is changed directly (that is, not through the 36 wrapper), and does not implement IItemSetChangeNotifier and/or 37 IPropertySetChangeNotifier the hierarchy information must be updated 38 with the L{updateHierarchicalWrapper} method. 39 40 @author: Vaadin Ltd. 41 @author: Richard Lincoln 42 """ 43
44 - def __init__(self, toBeWrapped):
45 """Constructs a new hierarchical wrapper for an existing Container. 46 Works even if the to-be-wrapped container already implements the 47 C{IHierarchical} interface. 48 49 @param toBeWrapped: 50 the container that needs to be accessed hierarchically 51 @see: L{updateHierarchicalWrapper} 52 """ 53 super(ContainerHierarchicalWrapper, self).__init__() 54 55 #: The wrapped container 56 self._container = None 57 58 #: Set of IDs of those contained Items that can't have children. 59 self._noChildrenAllowed = None 60 61 #: Mapping from Item ID to parent Item ID 62 self._parent = None 63 64 #: Mapping from Item ID to a list of child IDs 65 self._children = None 66 67 #: List that contains all root elements of the container. 68 self._roots = None 69 70 #: Is the wrapped container hierarchical by itself ? 71 self._hierarchical = None 72 73 self._container = toBeWrapped 74 self._hierarchical = isinstance(self._container, IHierarchical) 75 76 # Check arguments 77 if self._container is None: 78 raise ValueError, 'Null can not be wrapped' 79 80 # Create initial order if needed 81 if not self._hierarchical: 82 self._noChildrenAllowed = OrderedSet() 83 self._parent = dict() 84 self._children = dict() 85 self._roots = set(self._container.getItemIds()) 86 87 self.updateHierarchicalWrapper()
88 89
91 """Updates the wrapper's internal hierarchy data to include all Items 92 in the underlying container. If the contents of the wrapped container 93 change without the wrapper's knowledge, this method needs to be called 94 to update the hierarchy information of the Items. 95 """ 96 if not self._hierarchical: 97 98 # Recreate hierarchy and data structures if missing 99 if (self._noChildrenAllowed is None or self._parent is None 100 or self._children is None or self._roots is None): 101 # Check that the hierarchy is up-to-date 102 self._noChildrenAllowed = set() 103 self._parent = dict() 104 self._children = dict() 105 self._roots = OrderedSet(self._container.getItemIds()) 106 107 else: 108 109 # ensure order of root and child lists is same as in wrapped 110 # container 111 itemIds = self._container.getItemIds() 112 basedOnOrderFromWrappedContainer = \ 113 ListedItemsFirstComparator(itemIds) 114 115 # Calculate the set of all items in the hierarchy 116 s = set() 117 s = s.union(self._parent.keys()) 118 s = s.union(self._children.keys()) 119 s = s.union(self._roots) 120 121 # Remove unnecessary items 122 for idd in s: 123 if not self._container.containsId(idd): 124 self.removeFromHierarchyWrapper(idd) 125 126 # Add all the missing items 127 ids = self._container.getItemIds() 128 for idd in ids: 129 if not (idd in s): 130 self.addToHierarchyWrapper(idd) 131 s.add(idd) 132 133 arry = list(self._roots) 134 arry.sort(cmp=basedOnOrderFromWrappedContainer) 135 self._roots = OrderedSet() 136 for a in arry: 137 self._roots.add(a) 138 139 for obj in self._children.keys(): 140 object2 = self._children[obj] 141 object2.sort(cmp=basedOnOrderFromWrappedContainer)
142 143
144 - def removeFromHierarchyWrapper(self, itemId):
145 """Removes the specified Item from the wrapper's internal hierarchy 146 structure. 147 148 Note : The Item is not removed from the underlying Container. 149 150 @param itemId: 151 the ID of the item to remove from the hierarchy. 152 """ 153 oprhanedChildren = self._children.pop(itemId, None) 154 if oprhanedChildren is not None: 155 for obj in oprhanedChildren: 156 # make orphaned children root nodes 157 self.setParent(obj, None) 158 159 if itemId in self._roots: 160 self._roots.remove(itemId) 161 162 p = self._parent.get(itemId) 163 if p is not None: 164 c = self._children.get(p) 165 if c is not None: 166 c.remove(itemId) 167 168 if itemId in self._parent: 169 del self._parent[itemId] 170 171 if itemId in self._noChildrenAllowed: 172 self._noChildrenAllowed.remove(itemId)
173 174
175 - def addToHierarchyWrapper(self, itemId):
176 """Adds the specified Item specified to the internal hierarchy 177 structure. The new item is added as a root Item. The underlying 178 container is not modified. 179 180 @param itemId: 181 the ID of the item to add to the hierarchy. 182 """ 183 self._roots.add(itemId)
184 185
186 - def areChildrenAllowed(self, itemId):
187 # Can the specified Item have any children? 188 189 # If the wrapped container implements the method directly, use it 190 if self._hierarchical: 191 return self._container.areChildrenAllowed(itemId) 192 193 if itemId in self._noChildrenAllowed: 194 return False 195 196 return self.containsId(itemId)
197 198
199 - def getChildren(self, itemId):
200 # Gets the IDs of the children of the specified Item. 201 202 # If the wrapped container implements the method directly, use it 203 if self._hierarchical: 204 return self._container.getChildren(itemId) 205 206 c = self._children.get(itemId) 207 if c is None: 208 return None 209 210 return list(c)
211 212
213 - def getParent(self, itemId):
214 # Gets the ID of the parent of the specified Item. 215 216 # If the wrapped container implements the method directly, use it 217 if self._hierarchical: 218 return self._container.getParent(itemId) 219 220 return self._parent.get(itemId)
221 222
223 - def hasChildren(self, itemId):
224 # Is the Item corresponding to the given ID a leaf node? 225 226 # If the wrapped container implements the method directly, use it 227 if self._hierarchical: 228 return self._container.hasChildren(itemId) 229 230 return self._children.get(itemId) is not None
231 232
233 - def isRoot(self, itemId):
234 # Is the Item corresponding to the given ID a root node? 235 236 # If the wrapped container implements the method directly, use it 237 if self._hierarchical: 238 return self._container.isRoot(itemId) 239 240 if itemId in self._parent: 241 return False 242 243 return self.containsId(itemId)
244 245
246 - def rootItemIds(self):
247 # Gets the IDs of the root elements in the container. 248 249 # If the wrapped container implements the method directly, use it 250 if self._hierarchical: 251 return self._container.rootItemIds() 252 253 return list(self._roots)
254 255
256 - def setChildrenAllowed(self, itemId, childrenAllowed):
257 """Sets the given Item's capability to have children. If the Item 258 identified with the itemId already has children and the 259 areChildrenAllowed is false this method fails and C{False} 260 is returned; the children must be first explicitly removed with 261 L{setParent} or L{IContainer.removeItem}. 262 263 @param itemId: 264 the ID of the Item in the container whose child capability 265 is to be set. 266 @param childrenAllowed: 267 the boolean value specifying if the Item can have children 268 or not. 269 @return: C{True} if the operation succeeded, C{False} if not 270 """ 271 # If the wrapped container implements the method directly, use it 272 if self._hierarchical: 273 return self._container.setChildrenAllowed(itemId, childrenAllowed) 274 275 # Check that the item is in the container 276 if not self.containsId(itemId): 277 return False 278 279 # Update status 280 if childrenAllowed: 281 if itemId in self._noChildrenAllowed: 282 self._noChildrenAllowed.remove(itemId) 283 else: 284 self._noChildrenAllowed.add(itemId) 285 286 return True
287 288
289 - def setParent(self, itemId, newParentId):
290 """Sets the parent of an Item. The new parent item must exist and be 291 able to have children. 292 (C{canHaveChildren(newParentId) == True}). It is also 293 possible to detach a node from the hierarchy (and thus make it root) 294 by setting the parent C{None}. 295 296 @param itemId: 297 the ID of the item to be set as the child of the Item 298 identified with newParentId. 299 @param newParentId: 300 the ID of the Item that's to be the new parent of the Item 301 identified with itemId. 302 @return: C{True} if the operation succeeded, C{False} if not 303 """ 304 # If the wrapped container implements the method directly, use it 305 if self._hierarchical: 306 return self._container.setParent(itemId, newParentId) 307 308 # Check that the item is in the container 309 if not self.containsId(itemId): 310 return False 311 312 # Get the old parent 313 oldParentId = self._parent.get(itemId) 314 315 # Check if no change is necessary 316 if ((newParentId is None and oldParentId is None) 317 or (newParentId is not None and newParentId == oldParentId)): 318 return True 319 320 # Making root 321 if newParentId is None: 322 323 # Remove from old parents children list 324 l = self._children.get(oldParentId) 325 if l is not None: 326 l.remove(itemId) 327 if len(l) == 0: 328 del self._children[itemId] 329 330 # Add to be a root 331 self._roots.add(itemId) 332 333 # Update parent 334 self._parent.remove(itemId) 335 336 return True 337 338 # Check that the new parent exists in container and can have 339 # children 340 if ((not self.containsId(newParentId)) 341 or (newParentId in self._noChildrenAllowed)): 342 return False 343 344 # Check that setting parent doesn't result to a loop 345 o = newParentId 346 while o is not None and not (o == itemId): 347 o = self._parent.get(o) 348 349 if o is not None: 350 return False 351 352 # Update parent 353 self._parent[itemId] = newParentId 354 pcl = self._children.get(newParentId) 355 if pcl is None: 356 pcl = list() 357 self._children[newParentId] = pcl 358 pcl.append(itemId) 359 360 # Remove from old parent or root 361 if oldParentId is None: 362 self._roots.remove(itemId) 363 else: 364 l = self._children.get(oldParentId) 365 if l is not None: 366 l.remove(itemId) 367 if len(l) == 0: 368 self._children.remove(oldParentId) 369 370 return True
371 372
373 - def addItem(self, itemId=None):
374 """Adds a new Item by its ID to the underlying container and to the 375 hierarchy. Creates a new Item into the Container, assigns it an 376 automatic ID, and adds it to the hierarchy if C{itemId} is C{None}. 377 378 @param itemId: 379 the ID of the Item to be created. 380 @return: the added Item or C{None} if the operation failed. 381 @raise NotImplementedError: 382 if the addItem is not supported. 383 """ 384 if itemId is None: 385 idd = self._container.addItem() 386 if not self._hierarchical and idd is not None: 387 self.addToHierarchyWrapper(idd) 388 return idd 389 else: 390 item = self._container.addItem(itemId) 391 if not self._hierarchical and item is not None: 392 self.addToHierarchyWrapper(itemId) 393 return item
394 395
396 - def removeAllItems(self):
397 """Removes all items from the underlying container and from the 398 hierarchy. 399 400 @return: C{True} if the operation succeeded, C{False} if not 401 @raise NotImplementedError: 402 if the removeAllItems is not supported. 403 """ 404 success = self._container.removeAllItems() 405 406 if not self._hierarchical and success: 407 self._roots.clear() 408 self._parent.clear() 409 self._children.clear() 410 self._noChildrenAllowed.clear() 411 412 return success
413 414
415 - def removeItem(self, itemId):
416 """Removes an Item specified by the itemId from the underlying 417 container and from the hierarchy. 418 419 @param itemId: 420 the ID of the Item to be removed. 421 @return: C{True} if the operation succeeded, C{False} if not 422 @raise NotImplementedError: 423 if the removeItem is not supported. 424 """ 425 success = self._container.removeItem(itemId) 426 427 if not self._hierarchical and success: 428 self.removeFromHierarchyWrapper(itemId) 429 430 return success
431 432
433 - def removeItemRecursively(self, itemId):
434 """Removes the Item identified by given itemId and all its children. 435 436 @see: L{removeItem} 437 @param itemId: 438 the identifier of the Item to be removed 439 @return: true if the operation succeeded 440 """ 441 dummy = HierarchicalContainer() 442 return HierarchicalContainer.removeItemRecursively(dummy, self, itemId)
443 444
445 - def addContainerProperty(self, propertyId, typ, defaultValue):
446 """Adds a new Property to all Items in the Container. 447 448 @param propertyId: 449 the ID of the new Property. 450 @param typ: 451 the Data type of the new Property. 452 @param defaultValue: 453 the value all created Properties are initialized to. 454 @return: C{True} if the operation succeeded, C{False} if not 455 @raise NotImplementedError: 456 if the addContainerProperty is not supported. 457 """ 458 return self._container.addContainerProperty(propertyId, typ, 459 defaultValue)
460 461
462 - def removeContainerProperty(self, propertyId):
463 """Removes the specified Property from the underlying container and 464 from the hierarchy. 465 466 Note: The Property will be removed from all Items in the Container. 467 468 @param propertyId: 469 the ID of the Property to remove. 470 @return: C{True} if the operation succeeded, C{False} if not 471 @raise NotImplementedError: 472 if the removeContainerProperty is not supported. 473 """ 474 return self._container.removeContainerProperty(propertyId)
475 476
477 - def containsId(self, itemId):
478 # Does the container contain the specified Item? 479 return self._container.containsId(itemId)
480 481
482 - def getItem(self, itemId):
483 # Gets the specified Item from the container. 484 return self._container.getItem(itemId)
485 486
487 - def getItemIds(self):
488 # Gets the ID's of all Items stored in the Container 489 return self._container.getItemIds()
490 491
492 - def getContainerProperty(self, itemId, propertyId):
493 # Gets the Property identified by the given itemId and propertyId 494 # from the Container 495 return self._container.getContainerProperty(itemId, propertyId)
496 497
498 - def getContainerPropertyIds(self):
499 # Gets the ID's of all Properties stored in the Container 500 return self._container.getContainerPropertyIds()
501 502
503 - def getType(self, propertyId):
504 # Gets the data type of all Properties identified by the given 505 # Property ID. 506 return self._container.getType(propertyId)
507 508
509 - def size(self):
510 # Gets the number of Items in the Container. 511 return len(self._container)
512 513
514 - def __len__(self):
515 return self.size()
516 517
518 - def addListener(self, listener, iface=None):
519 if (isinstance(listener, IItemSetChangeListener) and 520 (iface is None or issubclass(iface, IItemSetChangeListener))): 521 # Registers a new Item set change listener for this Container. 522 if isinstance(self._container, IItemSetChangeNotifier): 523 pl = PiggybackListener(listener, self) 524 self._container.addListener(pl, IItemSetChangeListener) 525 526 if (isinstance(listener, IPropertySetChangeListener) and 527 (iface is None or 528 issubclass(iface, IPropertySetChangeListener))): 529 # Registers a new Property set change listener for this Container. 530 if isinstance(self._container, IPropertySetChangeNotifier): 531 pl = PiggybackListener(listener, self) 532 self._container.addListener(pl, IPropertySetChangeListener)
533 534
535 - def addCallback(self, callback, eventType=None, *args):
536 if eventType is None: 537 eventType = callback._eventType 538 539 if issubclass(eventType, IItemSetChangeEvent): 540 # Registers a new Item set change listener for this Container. 541 if isinstance(self._container, IItemSetChangeNotifier): 542 pl = PiggybackListener(callback, self, *args) 543 self._container.addListener(pl, IItemSetChangeListener) 544 545 elif issubclass(eventType, IPropertySetChangeEvent): 546 # Registers a new Property set change listener for this Container. 547 if isinstance(self._container, IPropertySetChangeNotifier): 548 pl = PiggybackListener(callback, self, *args) 549 self._container.addListener(pl, IPropertySetChangeListener) 550 551 else: 552 super(ContainerHierarchicalWrapper, self).addCallback(callback, 553 eventType, *args)
554 555
556 - def removeListener(self, listener, iface=None):
557 if (isinstance(listener, IItemSetChangeListener) and 558 (iface is None or issubclass(iface, IItemSetChangeListener))): 559 # Removes a Item set change listener from the object. 560 if isinstance(self._container, IItemSetChangeNotifier): 561 pl = PiggybackListener(listener, self) 562 self._container.removeListener(pl, IItemSetChangeListener) 563 564 565 if (isinstance(listener, IPropertySetChangeListener) and 566 (iface is None or issubclass(iface, IPropertySetChangeListener))): 567 # Removes a Property set change listener from the object. 568 if isinstance(self._container, IPropertySetChangeNotifier): 569 pl = PiggybackListener(listener, self) 570 self._container.removeListener(pl, IPropertySetChangeListener)
571 572
573 - def removeCallback(self, callback, eventType=None):
574 if eventType is None: 575 eventType = callback._eventType 576 577 if issubclass(eventType, IItemSetChangeEvent): 578 # Removes a Item set change listener from the object. 579 if isinstance(self._container, IItemSetChangeNotifier): 580 pl = PiggybackListener(callback, self) 581 self._container.removeListener(pl, IItemSetChangeListener) 582 583 elif issubclass(eventType, IPropertySetChangeEvent): 584 # Removes a Property set change listener from the object. 585 if isinstance(self._container, IPropertySetChangeNotifier): 586 pl = PiggybackListener(callback, self) 587 self._container.removeListener(pl, IPropertySetChangeListener) 588 589 else: 590 super(ContainerHierarchicalWrapper, self).removeCallback(callback, 591 eventType)
592 593
594 -class PiggybackListener(IContainer, IPropertySetChangeListener, 595 IItemSetChangeListener):
596 """This listener 'piggybacks' on the real listener in order to update the 597 wrapper when needed. It proxies equals() and hashCode() to the real 598 listener so that the correct listener gets removed. 599 """ 600
601 - def __init__(self, realListener, wrapper, *args):
602 self._listener = realListener 603 self._wrapper = wrapper 604 self._args = args
605 606
607 - def containerItemSetChange(self, event):
608 self._wrapper.updateHierarchicalWrapper() 609 if isinstance(self._listener, IItemSetChangeListener): 610 self._listener.containerItemSetChange(event) 611 else: 612 self._listener(event, *self._args)
613 614
615 - def containerPropertySetChange(self, event):
616 self._wrapper.updateHierarchicalWrapper() 617 if isinstance(self._listener, IPropertySetChangeListener): 618 self._listener.containerPropertySetChange(event) 619 else: 620 self._listener(event, *self._args)
621 622
623 - def __eq__(self, obj):
624 return (obj is not None and obj == self._listener)
625 626
627 - def __hash__(self):
628 return hash(self._listener)
629 630
631 -class ListedItemsFirstComparator(object):
632 """A comparator that sorts the listed items before other items. 633 Otherwise, the order is undefined. 634 """ 635
636 - def __init__(self, itemIds):
637 self._itemIds = itemIds
638 639
640 - def __call__(self, o1, o2):
641 return self.compare(o1, o2)
642 643
644 - def compare(self, o1, o2):
645 if o1 == o2: 646 return 0 647 for idd in self._itemIds: 648 if idd == o1: 649 return -1 650 elif idd == o2: 651 return 1 652 return 0
653