001: /* *************************************************************************
002:
003: Millstone(TM)
004: Open Sourced User Interface Library for
005: Internet Development with Java
006:
007: Millstone is a registered trademark of IT Mill Ltd
008: Copyright (C) 2000-2005 IT Mill Ltd
009:
010: *************************************************************************
011:
012: This library is free software; you can redistribute it and/or
013: modify it under the terms of the GNU Lesser General Public
014: license version 2.1 as published by the Free Software Foundation.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: *************************************************************************
026:
027: For more information, contact:
028:
029: IT Mill Ltd phone: +358 2 4802 7180
030: Ruukinkatu 2-4 fax: +358 2 4802 7181
031: 20540, Turku email: info@itmill.com
032: Finland company www: www.itmill.com
033:
034: Primary source for MillStone information and releases: www.millstone.org
035:
036: ********************************************************************** */
037:
038: package org.millstone.base.data.util;
039:
040: import java.util.Collection;
041: import java.util.Iterator;
042: import java.util.LinkedList;
043: import java.util.Collections;
044: import java.util.Hashtable;
045: import java.util.HashSet;
046: import org.millstone.base.data.Container;
047: import org.millstone.base.data.Item;
048: import org.millstone.base.data.Property;
049:
050: /** <p>A wrapper class for adding external hierarchy to containers not
051: * implementing the {@link org.millstone.base.data.Container.Hierarchical}
052: * interface.</p>
053: *
054: * <p>If the wrapped container is changed directly (that is, not through
055: * the wrapper), the hierarchy information must be updated with the
056: * {@link #updateHierarchicalWrapper()} method.</p>
057: *
058: * @author IT Mill Ltd.
059: * @version 3.1.1
060: * @since 3.0
061: */
062: public class ContainerHierarchicalWrapper implements
063: Container.Hierarchical, Container.ItemSetChangeNotifier,
064: Container.PropertySetChangeNotifier {
065:
066: /** The wrapped container */
067: private Container container;
068:
069: /** Set of IDs of those contained Items that can't have children. */
070: private HashSet noChildrenAllowed = null;
071:
072: /** Mapping from Item ID to parent Item */
073: private Hashtable parent = null;
074:
075: /** Mapping from Item ID to a list of child IDs */
076: private Hashtable children = null;
077:
078: /** List that contains all root elements of the container. */
079: private LinkedList roots = null;
080:
081: /** Is the wrapped container hierarchical by itself ? */
082: private boolean hierarchical;
083:
084: /** Constructs a new hierarchical wrapper for an existing Container.
085: * Works even if the to-be-wrapped container already implements the
086: * Container.Hierarchical interface.
087: *
088: * @param toBeWrapped the container that needs to be accessed
089: * hierarchically
090: */
091: public ContainerHierarchicalWrapper(Container toBeWrapped) {
092:
093: container = toBeWrapped;
094: hierarchical = container instanceof Container.Hierarchical;
095:
096: // Check arguments
097: if (container == null)
098: throw new NullPointerException("Null can not be wrapped");
099:
100: // Create initial order if needed
101: if (!hierarchical) {
102: noChildrenAllowed = new HashSet();
103: parent = new Hashtable();
104: children = new Hashtable();
105: roots = new LinkedList(container.getItemIds());
106: }
107:
108: updateHierarchicalWrapper();
109: }
110:
111: /** Updates the wrapper's internal hierarchy data to include all Items
112: * in the underlying container. If the contents of the wrapped container
113: * change without the wrapper's knowledge, this method needs to be
114: * called to update the hierarchy information of the Items.
115: */
116: public void updateHierarchicalWrapper() {
117:
118: if (!hierarchical) {
119:
120: // Recreate hierarchy and datasrtuctures if missing
121: if (noChildrenAllowed == null || parent == null
122: || children == null || roots == null) {
123: noChildrenAllowed = new HashSet();
124: parent = new Hashtable();
125: children = new Hashtable();
126: roots = new LinkedList(container.getItemIds());
127: }
128:
129: // Check that the hierarchy is up-to-date
130: else {
131:
132: // Calculate the set of all items in the hierarchy
133: HashSet s = new HashSet();
134: s.add(parent.keySet());
135: s.add(children.keySet());
136: s.addAll(roots);
137:
138: // Remove unnecessary items
139: for (Iterator i = s.iterator(); i.hasNext();) {
140: Object id = i.next();
141: if (!container.containsId(id))
142: removeFromHierarchyWrapper(id);
143: }
144:
145: // Add all the missing items
146: Collection ids = container.getItemIds();
147: for (Iterator i = ids.iterator(); i.hasNext();) {
148: Object id = i.next();
149: if (!s.contains(id)) {
150: addToHierarchyWrapper(id);
151: s.add(id);
152: }
153: }
154: }
155: }
156: }
157:
158: /** Removes the specified Item from the wrapper's internal hierarchy
159: * structure. Note that the Item is not removed from the underlying
160: * Container.
161: *
162: * @param itemId ID of the item to remove from the hierarchy
163: */
164: private void removeFromHierarchyWrapper(Object itemId) {
165:
166: if (isRoot(itemId))
167: roots.remove(itemId);
168: Object p = parent.get(itemId);
169: if (p != null) {
170: LinkedList c = (LinkedList) children.get(p);
171: if (c != null)
172: c.remove(itemId);
173: }
174: parent.remove(itemId);
175: children.remove(itemId);
176: noChildrenAllowed.remove(itemId);
177: }
178:
179: /** Adds the specified Item specified to the internal hierarchy
180: * structure. The new item is added as a root Item. The underlying
181: * container is not modified.
182: *
183: * @param itemId ID of the item to add to the hierarchy
184: */
185: private void addToHierarchyWrapper(Object itemId) {
186: roots.add(itemId);
187: }
188:
189: /* Can the specified Item have any children?
190: * Don't add a JavaDoc comment here, we use the default documentation
191: * from implemented interface.
192: */
193: public boolean areChildrenAllowed(Object itemId) {
194:
195: // If the wrapped container implements the method directly, use it
196: if (hierarchical)
197: return ((Container.Hierarchical) container)
198: .areChildrenAllowed(itemId);
199: return !noChildrenAllowed.contains(itemId);
200: }
201:
202: /* Get the IDs of the children of the specified Item.
203: * Don't add a JavaDoc comment here, we use the default documentation
204: * from implemented interface.
205: */
206: public Collection getChildren(Object itemId) {
207:
208: // If the wrapped container implements the method directly, use it
209: if (hierarchical)
210: return ((Container.Hierarchical) container)
211: .getChildren(itemId);
212:
213: Collection c = (Collection) children.get(itemId);
214: if (c == null)
215: return null;
216: return Collections.unmodifiableCollection(c);
217: }
218:
219: /* Get the ID of the parent of the specified Item.
220: * Don't add a JavaDoc comment here, we use the default documentation
221: * from implemented interface.
222: */
223: public Object getParent(Object itemId) {
224:
225: // If the wrapped container implements the method directly, use it
226: if (hierarchical)
227: return ((Container.Hierarchical) container)
228: .getParent(itemId);
229:
230: return parent.get(itemId);
231: }
232:
233: /* Is the Item corresponding to the given ID a leaf node?
234: * Don't add a JavaDoc comment here, we use the default documentation
235: * from implemented interface.
236: */
237: public boolean hasChildren(Object itemId) {
238:
239: // If the wrapped container implements the method directly, use it
240: if (hierarchical)
241: return ((Container.Hierarchical) container)
242: .hasChildren(itemId);
243:
244: return children.get(itemId) != null;
245: }
246:
247: /* Is the Item corresponding to the given ID a root node?
248: * Don't add a JavaDoc comment here, we use the default documentation
249: * from implemented interface.
250: */
251: public boolean isRoot(Object itemId) {
252:
253: // If the wrapped container implements the method directly, use it
254: if (hierarchical)
255: return ((Container.Hierarchical) container).isRoot(itemId);
256:
257: return parent.get(itemId) == null;
258: }
259:
260: /* Get the IDs of the root elements in the container.
261: * Don't add a JavaDoc comment here, we use the default documentation
262: * from implemented interface.
263: */
264: public Collection rootItemIds() {
265:
266: // If the wrapped container implements the method directly, use it
267: if (hierarchical)
268: return ((Container.Hierarchical) container).rootItemIds();
269:
270: return Collections.unmodifiableCollection(roots);
271: }
272:
273: /** <p>Sets the given Item's capability to have children. If the Item
274: * identified with <code>itemId</code> already has children and
275: * <code>areChildrenAllowed</code> is false this method fails and
276: * <code>false</code> is returned; the children must be first explicitly
277: * removed with {@link #setParent(Object itemId, Object newParentId)} or
278: * {@link org.millstone.base.data.Container#removeItem(Object itemId)}.</p>
279: *
280: * @param itemId ID of the Item in the container whose child
281: * capability is to be set
282: * @param childrenAllowed boolean value specifying if the Item
283: * can have children or not
284: * @return <code>true</code> if the operation succeeded,
285: * <code>false</code> if not
286: */
287: public boolean setChildrenAllowed(Object itemId,
288: boolean childrenAllowed) {
289:
290: // If the wrapped container implements the method directly, use it
291: if (hierarchical)
292: return ((Container.Hierarchical) container)
293: .setChildrenAllowed(itemId, childrenAllowed);
294:
295: // Check that the item is in the container
296: if (!containsId(itemId))
297: return false;
298:
299: // Update status
300: if (childrenAllowed)
301: noChildrenAllowed.remove(itemId);
302: else
303: noChildrenAllowed.add(itemId);
304:
305: return true;
306: }
307:
308: /** <p>Sets the parent of an Item. The new parent item must exist and be
309: * able to have children.
310: * (<code>canHaveChildren(newParentId) == true</code>). It is also
311: * possible to detach a node from the hierarchy (and thus make it root)
312: * by setting the parent <code>null</code>.</p>
313: *
314: * @param itemId ID of the item to be set as the child of the Item
315: * identified with <code>newParentId</code>
316: * @param newParentId ID of the Item that's to be the new parent
317: * of the Item identified with <code>itemId</code>
318: * @return <code>true</code> if the operation succeeded,
319: * <code>false</code> if not
320: */
321: public boolean setParent(Object itemId, Object newParentId) {
322:
323: // If the wrapped container implements the method directly, use it
324: if (hierarchical)
325: return ((Container.Hierarchical) container).setParent(
326: itemId, newParentId);
327:
328: // Check that the item is in the container
329: if (!containsId(itemId))
330: return false;
331:
332: // Get the old parent
333: Object oldParentId = parent.get(itemId);
334:
335: // Check if no change is necessary
336: if ((newParentId == null && oldParentId == null)
337: || newParentId.equals(oldParentId))
338: return true;
339:
340: // Making root
341: if (newParentId == null) {
342:
343: // Remove from old parents children list
344: LinkedList l = (LinkedList) children.get(itemId);
345: if (l != null) {
346: l.remove(itemId);
347: if (l.isEmpty())
348: children.remove(itemId);
349: }
350:
351: // Add to be a root
352: roots.add(itemId);
353:
354: // Update parent
355: parent.remove(itemId);
356:
357: return true;
358: }
359:
360: // Check that the new parent exists in container and can have
361: // children
362: if (!containsId(newParentId)
363: || noChildrenAllowed.contains(newParentId))
364: return false;
365:
366: // Check that setting parent doesn't result to a loop
367: Object o = newParentId;
368: while (o != null && !o.equals(itemId))
369: o = parent.get(o);
370: if (o != null)
371: return false;
372:
373: // Update parent
374: parent.put(itemId, newParentId);
375: LinkedList pcl = (LinkedList) children.get(newParentId);
376: if (pcl == null) {
377: pcl = new LinkedList();
378: children.put(newParentId, pcl);
379: }
380: pcl.add(itemId);
381:
382: // Remove from old parent or root
383: if (oldParentId == null)
384: roots.remove(itemId);
385: else {
386: LinkedList l = (LinkedList) children.get(oldParentId);
387: if (l != null) {
388: l.remove(itemId);
389: if (l.isEmpty())
390: children.remove(oldParentId);
391: }
392: }
393:
394: return true;
395: }
396:
397: /** Creates a new Item into the Container, assigns it an
398: * automatic ID, and adds it to the hierarchy.
399: *
400: * @return the autogenerated ID of the new Item or <code>null</code>
401: * if the operation failed
402: */
403: public Object addItem() throws UnsupportedOperationException {
404:
405: Object id = container.addItem();
406: if (id != null)
407: addToHierarchyWrapper(id);
408: return id;
409: }
410:
411: /** Adds a new Item by its ID to the underlying container and to the
412: * hierarchy.
413: *
414: * @return the added Item or <code>null</code> if the operation failed
415: */
416: public Item addItem(Object itemId)
417: throws UnsupportedOperationException {
418:
419: Item item = container.addItem(itemId);
420: if (item != null)
421: addToHierarchyWrapper(itemId);
422: return item;
423: }
424:
425: /** Removes all items from the underlying container and from the
426: * hierarcy.
427: *
428: * @return <code>true</code> if the operation succeeded,
429: * <code>false</code> if not
430: */
431: public boolean removeAllItems()
432: throws UnsupportedOperationException {
433:
434: boolean success = container.removeAllItems();
435:
436: if (success) {
437: roots.clear();
438: parent.clear();
439: children.clear();
440: noChildrenAllowed.clear();
441: }
442: return success;
443: }
444:
445: /** Removes an Item specified by <code>itemId</code> from the underlying
446: * container and from the hierarcy.
447: *
448: * @return <code>true</code> if the operation succeeded,
449: * <code>false</code> if not
450: */
451: public boolean removeItem(Object itemId)
452: throws UnsupportedOperationException {
453:
454: boolean success = container.removeItem(itemId);
455:
456: if (success)
457: removeFromHierarchyWrapper(itemId);
458:
459: return success;
460: }
461:
462: /** Adds a new Property to all Items in the Container.
463: *
464: * @param propertyId ID of the new Property
465: * @param type Data type of the new Property
466: * @param defaultValue The value all created Properties are
467: * initialized to
468: * @return <code>true</code> if the operation succeeded,
469: * <code>false</code> if not
470: */
471: public boolean addContainerProperty(Object propertyId, Class type,
472: Object defaultValue) throws UnsupportedOperationException {
473:
474: return container.addContainerProperty(propertyId, type,
475: defaultValue);
476: }
477:
478: /** Removes the specified Property from the underlying container and
479: * from the hierarchy. Note that the Property will be removed from all
480: * Items in the Container.
481: *
482: * @param propertyId ID of the Property to remove
483: * @return <code>true</code> if the operation succeeded,
484: * <code>false</code> if not
485: */
486: public boolean removeContainerProperty(Object propertyId)
487: throws UnsupportedOperationException {
488: return container.removeContainerProperty(propertyId);
489: }
490:
491: /* Does the container contain the specified Item?
492: * Don't add a JavaDoc comment here, we use the default documentation
493: * from implemented interface.
494: */
495: public boolean containsId(Object itemId) {
496: return container.containsId(itemId);
497: }
498:
499: /* Gets the specified Item from the container.
500: * Don't add a JavaDoc comment here, we use the default documentation
501: * from implemented interface.
502: */
503: public Item getItem(Object itemId) {
504: return container.getItem(itemId);
505: }
506:
507: /* Gets the ID's of all Items stored in the Container
508: * Don't add a JavaDoc comment here, we use the default documentation
509: * from implemented interface.
510: */
511: public Collection getItemIds() {
512: return container.getItemIds();
513: }
514:
515: /* Gets the Property identified by the given itemId and propertyId from
516: * the Container
517: * Don't add a JavaDoc comment here, we use the default documentation
518: * from implemented interface.
519: */
520: public Property getContainerProperty(Object itemId,
521: Object propertyId) {
522: return container.getContainerProperty(itemId, propertyId);
523: }
524:
525: /* Gets the ID's of all Properties stored in the Container
526: * Don't add a JavaDoc comment here, we use the default documentation
527: * from implemented interface.
528: */
529: public Collection getContainerPropertyIds() {
530: return container.getContainerPropertyIds();
531: }
532:
533: /* Gets the data type of all Properties identified by the given Property
534: * ID.
535: * Don't add a JavaDoc comment here, we use the default documentation
536: * from implemented interface.
537: */
538: public Class getType(Object propertyId) {
539: return container.getType(propertyId);
540: }
541:
542: /* Gets the number of Items in the Container.
543: * Don't add a JavaDoc comment here, we use the default documentation
544: * from implemented interface.
545: */
546: public int size() {
547: return container.size();
548: }
549:
550: /* Registers a new Item set change listener for this Container.
551: * Don't add a JavaDoc comment here, we use the default documentation
552: * from implemented interface.
553: */
554: public void addListener(Container.ItemSetChangeListener listener) {
555: if (container instanceof Container.ItemSetChangeNotifier)
556: ((Container.ItemSetChangeNotifier) container)
557: .addListener(listener);
558: }
559:
560: /* Removes a Item set change listener from the object.
561: * Don't add a JavaDoc comment here, we use the default documentation
562: * from implemented interface.
563: */
564: public void removeListener(Container.ItemSetChangeListener listener) {
565: if (container instanceof Container.ItemSetChangeNotifier)
566: ((Container.ItemSetChangeNotifier) container)
567: .removeListener(listener);
568: }
569:
570: /* Registers a new Property set change listener for this Container.
571: * Don't add a JavaDoc comment here, we use the default documentation
572: * from implemented interface.
573: */
574: public void addListener(Container.PropertySetChangeListener listener) {
575: if (container instanceof Container.PropertySetChangeNotifier)
576: ((Container.PropertySetChangeNotifier) container)
577: .addListener(listener);
578: }
579:
580: /* Removes a Property set change listener from the object.
581: * Don't add a JavaDoc comment here, we use the default documentation
582: * from implemented interface.
583: */
584: public void removeListener(
585: Container.PropertySetChangeListener listener) {
586: if (container instanceof Container.PropertySetChangeNotifier)
587: ((Container.PropertySetChangeNotifier) container)
588: .removeListener(listener);
589: }
590: }
|