1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
56 self._container = None
57
58
59 self._noChildrenAllowed = None
60
61
62 self._parent = None
63
64
65 self._children = None
66
67
68 self._roots = None
69
70
71 self._hierarchical = None
72
73 self._container = toBeWrapped
74 self._hierarchical = isinstance(self._container, IHierarchical)
75
76
77 if self._container is None:
78 raise ValueError, 'Null can not be wrapped'
79
80
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
99 if (self._noChildrenAllowed is None or self._parent is None
100 or self._children is None or self._roots is None):
101
102 self._noChildrenAllowed = set()
103 self._parent = dict()
104 self._children = dict()
105 self._roots = OrderedSet(self._container.getItemIds())
106
107 else:
108
109
110
111 itemIds = self._container.getItemIds()
112 basedOnOrderFromWrappedContainer = \
113 ListedItemsFirstComparator(itemIds)
114
115
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
122 for idd in s:
123 if not self._container.containsId(idd):
124 self.removeFromHierarchyWrapper(idd)
125
126
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
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
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
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
187
188
189
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
200
201
202
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
214
215
216
217 if self._hierarchical:
218 return self._container.getParent(itemId)
219
220 return self._parent.get(itemId)
221
222
224
225
226
227 if self._hierarchical:
228 return self._container.hasChildren(itemId)
229
230 return self._children.get(itemId) is not None
231
232
234
235
236
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
247
248
249
250 if self._hierarchical:
251 return self._container.rootItemIds()
252
253 return list(self._roots)
254
255
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
272 if self._hierarchical:
273 return self._container.setChildrenAllowed(itemId, childrenAllowed)
274
275
276 if not self.containsId(itemId):
277 return False
278
279
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
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
305 if self._hierarchical:
306 return self._container.setParent(itemId, newParentId)
307
308
309 if not self.containsId(itemId):
310 return False
311
312
313 oldParentId = self._parent.get(itemId)
314
315
316 if ((newParentId is None and oldParentId is None)
317 or (newParentId is not None and newParentId == oldParentId)):
318 return True
319
320
321 if newParentId is None:
322
323
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
331 self._roots.add(itemId)
332
333
334 self._parent.remove(itemId)
335
336 return True
337
338
339
340 if ((not self.containsId(newParentId))
341 or (newParentId in self._noChildrenAllowed)):
342 return False
343
344
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
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
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
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
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
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
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
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
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
480
481
483
484 return self._container.getItem(itemId)
485
486
490
491
496
497
501
502
504
505
506 return self._container.getType(propertyId)
507
508
510
511 return len(self._container)
512
513
516
517
519 if (isinstance(listener, IItemSetChangeListener) and
520 (iface is None or issubclass(iface, IItemSetChangeListener))):
521
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
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
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
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
557 if (isinstance(listener, IItemSetChangeListener) and
558 (iface is None or issubclass(iface, IItemSetChangeListener))):
559
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
568 if isinstance(self._container, IPropertySetChangeNotifier):
569 pl = PiggybackListener(listener, self)
570 self._container.removeListener(pl, IPropertySetChangeListener)
571
572
574 if eventType is None:
575 eventType = callback._eventType
576
577 if issubclass(eventType, IItemSetChangeEvent):
578
579 if isinstance(self._container, IItemSetChangeNotifier):
580 pl = PiggybackListener(callback, self)
581 self._container.removeListener(pl, IItemSetChangeListener)
582
583 elif issubclass(eventType, IPropertySetChangeEvent):
584
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
613
614
621
622
624 return (obj is not None and obj == self._listener)
625
626
628 return hash(self._listener)
629
630
632 """A comparator that sorts the listed items before other items.
633 Otherwise, the order is undefined.
634 """
635
637 self._itemIds = itemIds
638
639
642
643
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