0001: /* *************************************************************************
0002:
0003: Millstone(TM)
0004: Open Sourced User Interface Library for
0005: Internet Development with Java
0006:
0007: Millstone is a registered trademark of IT Mill Ltd
0008: Copyright (C) 2000-2005 IT Mill Ltd
0009:
0010: *************************************************************************
0011:
0012: This library is free software; you can redistribute it and/or
0013: modify it under the terms of the GNU Lesser General Public
0014: license version 2.1 as published by the Free Software Foundation.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: *************************************************************************
0026:
0027: For more information, contact:
0028:
0029: IT Mill Ltd phone: +358 2 4802 7180
0030: Ruukinkatu 2-4 fax: +358 2 4802 7181
0031: 20540, Turku email: info@itmill.com
0032: Finland company www: www.itmill.com
0033:
0034: Primary source for MillStone information and releases: www.millstone.org
0035:
0036: ********************************************************************** */
0037:
0038: package org.millstone.base.data.util;
0039:
0040: import java.util.*;
0041: import java.util.HashSet;
0042: import java.util.LinkedList;
0043: import java.util.Iterator;
0044: import java.util.Hashtable;
0045: import java.util.Collections;
0046: import java.util.Collection;
0047: import java.util.EventObject;
0048: import java.lang.reflect.Constructor;
0049: import org.millstone.base.data.Container;
0050: import org.millstone.base.data.Item;
0051: import org.millstone.base.data.Property;
0052:
0053: /**
0054: * Indexed container implementation.
0055: * <p>
0056: * A list implementation of the org.millstone.base.data.Container interface. A
0057: * list is a ordered collection wherein the user has a precise control over
0058: * where in the list each new Item is inserted. The user may access the Items by
0059: * their integer index (position in the list) or by their Item ID.
0060: * </p>
0061: *
0062: * @see org.millstone.base.data.Container
0063: *
0064: * @author IT Mill Ltd.
0065: * @version
0066: * 3.1.1
0067: * @since 3.0
0068: */
0069:
0070: public class IndexedContainer implements Container, Container.Indexed,
0071: Container.ItemSetChangeNotifier,
0072: Container.PropertySetChangeNotifier,
0073: Property.ValueChangeNotifier, Container.Sortable, Comparator,
0074: Cloneable {
0075:
0076: /* Internal structure *************************************************** */
0077:
0078: /** Linked list of ordered Item IDs */
0079: private ArrayList itemIds = new ArrayList();
0080:
0081: /** Linked list of ordered Property IDs */
0082: private ArrayList propertyIds = new ArrayList();
0083:
0084: /** Property ID to type mapping */
0085: private Hashtable types = new Hashtable();
0086:
0087: /**
0088: * Hash of Items, where each Item is implemented as a mapping from Property
0089: * ID to Property value.
0090: */
0091: private Hashtable items = new Hashtable();
0092:
0093: /**
0094: * Set of properties that are read-only.
0095: */
0096: private HashSet readOnlyProperties = new HashSet();
0097:
0098: /**
0099: * List of all Property value change event listeners listening all the
0100: * properties
0101: */
0102: private LinkedList propertyValueChangeListeners = null;
0103:
0104: /**
0105: * Data structure containing all listeners interested in changes to single
0106: * Properties. The data structure is a hashtable mapping Property IDs to a
0107: * hashtable that maps Item IDs to a linked list of listeners listening
0108: * Property identified by given Property ID and Item ID.
0109: */
0110: private Hashtable singlePropertyValueChangeListeners = null;
0111:
0112: /** List of all Property set change event listeners */
0113: private LinkedList propertySetChangeListeners = null;
0114:
0115: /** List of all container Item set change event listeners */
0116: private LinkedList itemSetChangeListeners = null;
0117:
0118: /** Temporary store for sorting property ids */
0119: private Object[] sortPropertyId;
0120:
0121: /** Temporary store for sorting direction */
0122: private boolean[] sortDirection;
0123:
0124: /* Container constructors *********************************************** */
0125:
0126: public IndexedContainer() {
0127: }
0128:
0129: public IndexedContainer(Collection itemIds) {
0130: if (items != null) {
0131: for (Iterator i = itemIds.iterator(); i.hasNext();) {
0132: this .addItem(i.next());
0133: }
0134: }
0135: }
0136:
0137: /* Container methods **************************************************** */
0138:
0139: /**
0140: * Gets the Item with the given Item ID from the list. If the list does not
0141: * contain the requested Item, <code>null</code> is returned.
0142: *
0143: * @param itemId
0144: * ID of the Item to retrieve
0145: * @return the Item with the given ID or <code>null</code> if the Item is
0146: * not found in the list
0147: */
0148: public Item getItem(Object itemId) {
0149: if (items.containsKey(itemId))
0150: return new IndexedContainerItem(itemId);
0151: return null;
0152: }
0153:
0154: /**
0155: * Gets the ID's of all Items stored in the list. The ID's are returned as a
0156: * unmodifiable collection.
0157: *
0158: * @return unmodifiable collection of Item IDs
0159: */
0160: public Collection getItemIds() {
0161: return Collections.unmodifiableCollection(itemIds);
0162: }
0163:
0164: /**
0165: * Gets the ID's of all Properties stored in the list. The ID's are returned
0166: * as a unmodifiable collection.
0167: *
0168: * @return unmodifiable collection of Property IDs
0169: */
0170: public Collection getContainerPropertyIds() {
0171: return Collections.unmodifiableCollection(propertyIds);
0172: }
0173:
0174: /**
0175: * Gets the type of a Property stored in the list.
0176: *
0177: * @param id
0178: * ID of the Property
0179: * @return Type of the requested Property
0180: */
0181: public Class getType(Object propertyId) {
0182: return (Class) types.get(propertyId);
0183: }
0184:
0185: /**
0186: * Gets the Property identified by the given Item ID and Property ID from
0187: * the lsit. If the list does not contain the Property, <code>null</code>
0188: * is returned.
0189: *
0190: * @param itemId
0191: * ID of the Item which contains the requested Property
0192: * @param propertyId
0193: * ID of the Property to retrieve
0194: * @return Property with the given ID or <code>null</code>
0195: *
0196: * @see org.millstone.base.data.Container#getContainerProperty(Object,
0197: * Object)
0198: */
0199: public Property getContainerProperty(Object itemId,
0200: Object propertyId) {
0201: if (!items.containsKey(itemId))
0202: return null;
0203: return new IndexedContainerProperty(itemId, propertyId);
0204: }
0205:
0206: /**
0207: * Gets the number of Items in the list.
0208: *
0209: * @return number of Items in the list
0210: */
0211: public int size() {
0212: return itemIds.size();
0213: }
0214:
0215: /**
0216: * Tests if the list contains the specified Item
0217: *
0218: * @param itemId
0219: * ID the of Item to be tested for
0220: * @return <code>true</code> if the operation succeeded,
0221: * <code>false</code> if not
0222: */
0223: public boolean containsId(Object itemId) {
0224: return items.containsKey(itemId);
0225: }
0226:
0227: /**
0228: * Add a new Property to all Items in the list. The Property ID, data type
0229: * and default value of the new Property are given as parameters.
0230: *
0231: * @param propertyId
0232: * ID of the new Property
0233: * @param type
0234: * Data type of the new Property
0235: * @param defaultValue
0236: * The value all created Properties are initialized to
0237: * @return <code>true</code> if the operation succeeded,
0238: * <code>false</code> if not
0239: */
0240: public boolean addContainerProperty(Object propertyId, Class type,
0241: Object defaultValue) {
0242:
0243: // Fails, if nulls are given
0244: if (propertyId == null || type == null)
0245: return false;
0246:
0247: // Fails if the Property is already present
0248: if (propertyIds.contains(propertyId))
0249: return false;
0250:
0251: // Add the Property to Property list and types
0252: propertyIds.add(propertyId);
0253: types.put(propertyId, type);
0254:
0255: // If default value is given, set it
0256: if (defaultValue != null)
0257: for (Iterator i = itemIds.iterator(); i.hasNext();)
0258: getItem(i.next()).getItemProperty(propertyId).setValue(
0259: defaultValue);
0260:
0261: // Send a change event
0262: fireContainerPropertySetChange();
0263:
0264: return true;
0265: }
0266:
0267: /**
0268: * Remove all Items from the list. Note that Property ID and type
0269: * information is preserved.
0270: *
0271: * @return <code>true</code> if the operation succeeded,
0272: * <code>false</code> if not
0273: */
0274: public boolean removeAllItems() {
0275:
0276: // Remove all Items
0277: itemIds.clear();
0278: items.clear();
0279:
0280: // Send a change event
0281: fireContentsChange();
0282:
0283: return true;
0284: }
0285:
0286: /**
0287: * Create a new Item into the list, and assign it an automatic ID. The new
0288: * ID is returned, or <code>null</code> if the operation fails. After a
0289: * successful call you can use the
0290: * {@link #getItem(Object ItemId) <code>getItem</code>}method to fetch the
0291: * Item.
0292: *
0293: * @return ID of the newly created Item, or <code>null</code> in case of a
0294: * failure
0295: */
0296: public Object addItem() {
0297:
0298: // Create a new id
0299: Object id = new Object();
0300:
0301: // Add the Item into container
0302: addItem(id);
0303:
0304: return id;
0305: }
0306:
0307: /**
0308: * Create a new Item with the given ID into the list. The new Item is
0309: * returned, and it is ready to have its Properties modified. Returns
0310: * <code>null</code> if the operation fails or the Container already
0311: * contains a Item with the given ID.
0312: *
0313: * @param itemId
0314: * ID of the Item to be created
0315: * @return Created new Item, or <code>null</code> in case of a failure
0316: */
0317: public Item addItem(Object itemId) {
0318:
0319: // Make sure that the Item has not been created yet
0320: if (items.containsKey(itemId))
0321: return null;
0322:
0323: // Add the Item to container
0324: itemIds.add(itemId);
0325: items.put(itemId, new Hashtable());
0326:
0327: // Send the event
0328: fireContentsChange();
0329:
0330: return getItem(itemId);
0331: }
0332:
0333: /**
0334: * Remove the Item corresponding to the given Item ID from the list.
0335: *
0336: * @param itemId
0337: * ID of the Item to remove
0338: * @return <code>true</code> if the operation succeeded,
0339: * <code>false</code> if not
0340: */
0341: public boolean removeItem(Object itemId) {
0342:
0343: if (items.remove(itemId) == null)
0344: return false;
0345: itemIds.remove(itemId);
0346:
0347: fireContentsChange();
0348:
0349: return true;
0350: }
0351:
0352: /**
0353: * Remove a Property specified by the given Property ID from the list. Note
0354: * that the Property will be removed from all Items in the list.
0355: *
0356: * @param propertyId
0357: * ID of the Property to remove
0358: * @return <code>true</code> if the operation succeeded,
0359: * <code>false</code> if not
0360: */
0361: public boolean removeContainerProperty(Object propertyId) {
0362:
0363: // Fails if the Property is not present
0364: if (!propertyIds.contains(propertyId))
0365: return false;
0366:
0367: // Remove the Property to Property list and types
0368: propertyIds.remove(propertyId);
0369: types.remove(propertyId);
0370:
0371: // If remove the Property from all Items
0372: for (Iterator i = itemIds.iterator(); i.hasNext();)
0373: ((Hashtable) items.get(i.next())).remove(propertyId);
0374:
0375: // Send a change event
0376: fireContainerPropertySetChange();
0377:
0378: return true;
0379: }
0380:
0381: /* Container.Ordered methods ******************************************** */
0382:
0383: /**
0384: * Gets the ID of the first Item in the list.
0385: *
0386: * @return ID of the first Item in the list
0387: */
0388: public Object firstItemId() {
0389: try {
0390: return itemIds.get(0);
0391: } catch (IndexOutOfBoundsException e) {
0392: }
0393: return null;
0394: }
0395:
0396: /**
0397: * Gets the ID of the last Item in the list.
0398: *
0399: * @return ID of the last Item in the list
0400: */
0401: public Object lastItemId() {
0402: try {
0403: return itemIds.get(itemIds.size() - 1);
0404: } catch (IndexOutOfBoundsException e) {
0405: }
0406: return null;
0407: }
0408:
0409: /**
0410: * Gets the ID of the Item following the Item that corresponds to
0411: * <code>itemId</code>. If the given Item is the last or not found in the
0412: * list, <code>null</code> is returned.
0413: *
0414: * @param itemId
0415: * ID of an Item in the list
0416: * @return ID of the next Item or <code>null</code>
0417: */
0418: public Object nextItemId(Object itemId) {
0419: try {
0420: return itemIds.get(itemIds.indexOf(itemId) + 1);
0421: } catch (IndexOutOfBoundsException e) {
0422: return null;
0423: }
0424: }
0425:
0426: /**
0427: * Gets the ID of the Item preceding the Item that corresponds to
0428: * <code>itemId</code>. If the given Item is the first or not found in
0429: * the list, <code>null</code> is returned.
0430: *
0431: * @param itemId
0432: * ID of an Item in the list
0433: * @return ID of the previous Item or <code>null</code>
0434: */
0435: public Object prevItemId(Object itemId) {
0436: try {
0437: return itemIds.get(itemIds.indexOf(itemId) - 1);
0438: } catch (IndexOutOfBoundsException e) {
0439: return null;
0440: }
0441: }
0442:
0443: /**
0444: * Tests if the Item corresponding to the given Item ID is the first Item in
0445: * the list.
0446: *
0447: * @param itemId
0448: * ID of an Item in the list
0449: * @return <code>true</code> if the Item is first in the list,
0450: * <code>false</code> if not
0451: */
0452: public boolean isFirstId(Object itemId) {
0453: return (size() >= 1 && itemIds.get(0).equals(itemId));
0454: }
0455:
0456: /**
0457: * Tests if the Item corresponding to the given Item ID is the last Item in
0458: * the list.
0459: *
0460: * @param itemId
0461: * ID of an Item in the list
0462: * @return <code>true</code> if the Item is last in the list,
0463: * <code>false</code> if not
0464: */
0465: public boolean isLastId(Object itemId) {
0466: int s = size();
0467: return (s >= 1 && itemIds.get(s - 1).equals(itemId));
0468: }
0469:
0470: /**
0471: * @see org.millstone.base.data.Container.Ordered#addItemAfter(Object,
0472: * Object)
0473: */
0474: public Item addItemAfter(Object previousItemId, Object newItemId) {
0475:
0476: // Get the index of the addition
0477: int index = 0;
0478: if (previousItemId != null) {
0479: index = 1 + indexOfId(previousItemId);
0480: if (index <= 0 || index > size())
0481: return null;
0482: }
0483:
0484: return addItemAt(index, newItemId);
0485: }
0486:
0487: /**
0488: * @see org.millstone.base.data.Container.Ordered#addItemAfter(Object)
0489: */
0490: public Object addItemAfter(Object previousItemId) {
0491:
0492: // Get the index of the addition
0493: int index = 0;
0494: if (previousItemId != null) {
0495: index = 1 + indexOfId(previousItemId);
0496: if (index <= 0 || index > size())
0497: return null;
0498: }
0499:
0500: return addItemAt(index);
0501: }
0502:
0503: /**
0504: * Get ID with the index. The following is true for the index: 0 <= index <
0505: * size().
0506: *
0507: * @return ID in the given index.
0508: * @param index
0509: * Index of the requested ID in the container.
0510: */
0511: public Object getIdByIndex(int index) {
0512: return itemIds.get(index);
0513: }
0514:
0515: /**
0516: * Get the index of an id. The following is true for the index: 0 <= index <
0517: * size().
0518: *
0519: * @return Index of the Item or -1 if the Item is not in the container.
0520: * @param itemId
0521: * ID of an Item in the collection
0522: */
0523: public int indexOfId(Object itemId) {
0524: return itemIds.indexOf(itemId);
0525: }
0526:
0527: /**
0528: * @see org.millstone.base.data.Container.Indexed#addItemAt(int, Object)
0529: */
0530: public Item addItemAt(int index, Object newItemId) {
0531:
0532: // Make sure that the Item has not been created yet
0533: if (items.containsKey(newItemId))
0534: return null;
0535:
0536: // Add the Item to container
0537: itemIds.add(index, newItemId);
0538: items.put(newItemId, new Hashtable());
0539:
0540: // Send the event
0541: fireContentsChange();
0542:
0543: return getItem(newItemId);
0544: }
0545:
0546: /**
0547: * @see org.millstone.base.data.Container.Indexed#addItemAt(int)
0548: */
0549: public Object addItemAt(int index) {
0550:
0551: // Create a new id
0552: Object id = new Object();
0553:
0554: // Add the Item into container
0555: addItemAt(index, id);
0556:
0557: return id;
0558: }
0559:
0560: /* Event notifiers ****************************************************** */
0561:
0562: /**
0563: * An <code>event</code> object specifying the list whose Property set has
0564: * changed.
0565: *
0566: * @author IT Mill Ltd.
0567: * @version
0568: * 3.1.1
0569: * @since 3.0
0570: */
0571: private class PropertySetChangeEvent extends EventObject implements
0572: Container.PropertySetChangeEvent {
0573:
0574: /**
0575: * Serial generated by eclipse.
0576: */
0577: private static final long serialVersionUID = 3257002172528079926L;
0578:
0579: private PropertySetChangeEvent(IndexedContainer source) {
0580: super (source);
0581: }
0582:
0583: /**
0584: * Gets the list whose Property set has changed.
0585: *
0586: * @return source object of the event as a Container
0587: */
0588: public Container getContainer() {
0589: return (Container) getSource();
0590: }
0591: }
0592:
0593: /**
0594: * An <code>event</code> object specifying the list whose Item set has
0595: * changed.
0596: *
0597: * @author IT Mill Ltd.
0598: * @version
0599: * 3.1.1
0600: * @since 3.0
0601: */
0602: private class ItemSetChangeEvent extends EventObject implements
0603: Container.ItemSetChangeEvent {
0604:
0605: /**
0606: * Serial generated by eclipse.
0607: */
0608: private static final long serialVersionUID = 3832616279386372147L;
0609:
0610: private ItemSetChangeEvent(IndexedContainer source) {
0611: super (source);
0612: }
0613:
0614: /**
0615: * Gets the list whose Item set has changed.
0616: *
0617: * @return source object of the event as a Container
0618: */
0619: public Container getContainer() {
0620: return (Container) getSource();
0621: }
0622:
0623: }
0624:
0625: /**
0626: * An <code>event</code> object specifying the Propery in a list whose
0627: * value has changed.
0628: *
0629: * @author IT Mill Ltd.
0630: * @version
0631: * 3.1.1
0632: * @since 3.0
0633: */
0634: private class PropertyValueChangeEvent extends EventObject
0635: implements Property.ValueChangeEvent {
0636:
0637: /**
0638: * Serial generated by eclipse.
0639: */
0640: private static final long serialVersionUID = 3833749884498359857L;
0641:
0642: private PropertyValueChangeEvent(Property source) {
0643: super (source);
0644: }
0645:
0646: /**
0647: * Gets the Property whose value has changed.
0648: *
0649: * @return source object of the event as a Property
0650: */
0651: public Property getProperty() {
0652: return (Property) getSource();
0653: }
0654:
0655: }
0656:
0657: /**
0658: * Registers a new Property set change listener for this list.
0659: *
0660: * @param listener
0661: * the new Listener to be registered
0662: */
0663: public void addListener(Container.PropertySetChangeListener listener) {
0664: if (propertySetChangeListeners == null)
0665: propertySetChangeListeners = new LinkedList();
0666: propertySetChangeListeners.add(listener);
0667: }
0668:
0669: /**
0670: * Removes a previously registered Property set change listener.
0671: *
0672: * @param listener
0673: * listener to be removed
0674: */
0675: public void removeListener(
0676: Container.PropertySetChangeListener listener) {
0677: if (propertySetChangeListeners != null)
0678: propertySetChangeListeners.remove(listener);
0679: }
0680:
0681: /**
0682: * Adds a Item set change listener for the list.
0683: *
0684: * @param listener
0685: * listener to be added
0686: */
0687: public void addListener(Container.ItemSetChangeListener listener) {
0688: if (itemSetChangeListeners == null)
0689: itemSetChangeListeners = new LinkedList();
0690: itemSetChangeListeners.add(listener);
0691: }
0692:
0693: /**
0694: * Removes a Item set change listener from the object.
0695: *
0696: * @param listener
0697: * listener to be removed
0698: */
0699: public void removeListener(Container.ItemSetChangeListener listener) {
0700: if (itemSetChangeListeners != null)
0701: itemSetChangeListeners.remove(listener);
0702: }
0703:
0704: /**
0705: * Registers a new value change listener for this object.
0706: *
0707: * @param listener
0708: * the new Listener to be registered
0709: */
0710: public void addListener(Property.ValueChangeListener listener) {
0711: if (propertyValueChangeListeners == null)
0712: propertyValueChangeListeners = new LinkedList();
0713: propertyValueChangeListeners.add(listener);
0714: }
0715:
0716: /**
0717: * Removes a previously registered value change listener.
0718: *
0719: * @param listener
0720: * listener to be removed
0721: */
0722: public void removeListener(Property.ValueChangeListener listener) {
0723: if (propertyValueChangeListeners != null)
0724: propertyValueChangeListeners.remove(listener);
0725: }
0726:
0727: /** Send a Property value change event to all interested listeners */
0728: private void firePropertyValueChange(IndexedContainerProperty source) {
0729:
0730: // Send event to listeners listening all value changes
0731: if (propertyValueChangeListeners != null) {
0732: Object[] l = propertyValueChangeListeners.toArray();
0733: Property.ValueChangeEvent event = new IndexedContainer.PropertyValueChangeEvent(
0734: source);
0735: for (int i = 0; i < l.length; i++)
0736: ((Property.ValueChangeListener) l[i])
0737: .valueChange(event);
0738: }
0739:
0740: // Send event to single property value change listeners
0741: if (singlePropertyValueChangeListeners != null) {
0742: Hashtable propertySetToListenerListMap = (Hashtable) singlePropertyValueChangeListeners
0743: .get(source.propertyId);
0744: if (propertySetToListenerListMap != null) {
0745: LinkedList listenerList = (LinkedList) propertySetToListenerListMap
0746: .get(source.itemId);
0747: if (listenerList != null) {
0748: Property.ValueChangeEvent event = new IndexedContainer.PropertyValueChangeEvent(
0749: source);
0750: for (Iterator i = listenerList.iterator(); i
0751: .hasNext();)
0752: ((Property.ValueChangeListener) i.next())
0753: .valueChange(event);
0754: }
0755: }
0756: }
0757:
0758: }
0759:
0760: /** Send a Property set change event to all interested listeners */
0761: private void fireContainerPropertySetChange() {
0762: if (propertySetChangeListeners != null) {
0763: Object[] l = propertySetChangeListeners.toArray();
0764: Container.PropertySetChangeEvent event = new IndexedContainer.PropertySetChangeEvent(
0765: this );
0766: for (int i = 0; i < l.length; i++)
0767: ((Container.PropertySetChangeListener) l[i])
0768: .containerPropertySetChange(event);
0769: }
0770: }
0771:
0772: /** Send Item set change event to all registered interested listeners */
0773: private void fireContentsChange() {
0774: if (itemSetChangeListeners != null) {
0775: Object[] l = itemSetChangeListeners.toArray();
0776: Container.ItemSetChangeEvent event = new IndexedContainer.ItemSetChangeEvent(
0777: this );
0778: for (int i = 0; i < l.length; i++)
0779: ((Container.ItemSetChangeListener) l[i])
0780: .containerItemSetChange(event);
0781: }
0782: }
0783:
0784: /** Add new single Property change listener */
0785: private void addSinglePropertyChangeListener(Object propertyId,
0786: Object itemId, Property.ValueChangeListener listener) {
0787: if (listener != null) {
0788: if (singlePropertyValueChangeListeners == null)
0789: singlePropertyValueChangeListeners = new Hashtable();
0790: Hashtable propertySetToListenerListMap = (Hashtable) singlePropertyValueChangeListeners
0791: .get(propertyId);
0792: if (propertySetToListenerListMap == null) {
0793: propertySetToListenerListMap = new Hashtable();
0794: singlePropertyValueChangeListeners.put(propertyId,
0795: propertySetToListenerListMap);
0796: }
0797: LinkedList listenerList = (LinkedList) propertySetToListenerListMap
0798: .get(itemId);
0799: if (listenerList == null) {
0800: listenerList = new LinkedList();
0801: propertySetToListenerListMap.put(itemId, listenerList);
0802: }
0803: listenerList.addLast(listener);
0804: }
0805: }
0806:
0807: /** Remove a previously registered single Property change listener */
0808: private void removeSinglePropertyChangeListener(Object propertyId,
0809: Object itemId, Property.ValueChangeListener listener) {
0810: if (listener != null
0811: && singlePropertyValueChangeListeners != null) {
0812: Hashtable propertySetToListenerListMap = (Hashtable) singlePropertyValueChangeListeners
0813: .get(propertyId);
0814: if (propertySetToListenerListMap != null) {
0815: LinkedList listenerList = (LinkedList) propertySetToListenerListMap
0816: .get(itemId);
0817: if (listenerList != null) {
0818: listenerList.remove(listener);
0819: if (listenerList.isEmpty())
0820: propertySetToListenerListMap.remove(itemId);
0821: }
0822: if (propertySetToListenerListMap.isEmpty())
0823: singlePropertyValueChangeListeners
0824: .remove(propertyId);
0825: }
0826: if (singlePropertyValueChangeListeners.isEmpty())
0827: singlePropertyValueChangeListeners = null;
0828: }
0829: }
0830:
0831: /* Internal Item and Property implementations *************************** */
0832:
0833: /*
0834: * A class implementing the org.millstone.base.data.Item interface to be
0835: * contained in the list. @author IT Mill Ltd.
0836: *
0837: * @version 3.1.1
0838: * @since 3.0
0839: */
0840: class IndexedContainerItem implements Item {
0841:
0842: /** Item ID in the host container for this Item */
0843: private Object itemId;
0844:
0845: /**
0846: * Constructs a new ListItem instance and connects it to a host
0847: * container.
0848: *
0849: * @param host
0850: * the list that contains this Item
0851: * @param itemId
0852: * Item ID of the new Item
0853: */
0854: private IndexedContainerItem(Object itemId) {
0855:
0856: // Get the item contents from the host
0857: if (itemId == null)
0858: throw new NullPointerException();
0859: this .itemId = itemId;
0860: }
0861:
0862: /**
0863: * Gets the Property corresponding to the given Property ID stored in
0864: * the Item. If the Item does not contain the Property,
0865: * <code>null</code> is returned.
0866: *
0867: * @param id
0868: * identifier of the Property to get
0869: * @return the Property with the given ID or <code>null</code>
0870: */
0871: public Property getItemProperty(Object id) {
0872: return new IndexedContainerProperty(itemId, id);
0873: }
0874:
0875: /**
0876: * Gets the collection containing the IDs of all Properties stored in
0877: * the Item.
0878: *
0879: * @return unmodifiable collection contaning IDs of the Properties
0880: * stored the Item
0881: */
0882: public Collection getItemPropertyIds() {
0883: return Collections.unmodifiableCollection(propertyIds);
0884: }
0885:
0886: /**
0887: * Gets the <code>String</code> representation of the contents of the
0888: * Item. The format of the string is a space separated catenation of the
0889: * <code>String</code> representations of the Properties contained by
0890: * the Item.
0891: *
0892: * @return <code>String</code> representation of the Item contents
0893: */
0894: public String toString() {
0895: String retValue = "";
0896:
0897: for (Iterator i = propertyIds.iterator(); i.hasNext();) {
0898: Object propertyId = i.next();
0899: retValue += getItemProperty(propertyId).toString();
0900: if (i.hasNext())
0901: retValue += " ";
0902: }
0903:
0904: return retValue;
0905: }
0906:
0907: /**
0908: * Calculates a integer hash-code for the Item that's unique inside the
0909: * list. Two Items inside the same list have always different
0910: * hash-codes, though Items in different lists may have identical
0911: * hash-codes.
0912: *
0913: * @return A locally unique hash-code as integer
0914: */
0915: public int hashCode() {
0916: return getHost().hashCode() ^ itemId.hashCode();
0917: }
0918:
0919: /**
0920: * Tests if the given object is the same as the this object. Two Items
0921: * got from a list container with the same ID are equal.
0922: *
0923: * @param obj
0924: * an object to compare with this object
0925: * @return <code>true</code> if the given object is the same as this
0926: * object, <code>false</code> if not
0927: */
0928: public boolean equals(Object obj) {
0929: if (obj == null
0930: || !obj.getClass().equals(
0931: IndexedContainerItem.class))
0932: return false;
0933: IndexedContainerItem li = (IndexedContainerItem) obj;
0934: return getHost() == li.getHost()
0935: && itemId.equals(li.itemId);
0936: }
0937:
0938: private IndexedContainer getHost() {
0939: return IndexedContainer.this ;
0940: }
0941:
0942: /**
0943: * Indexed container does not support adding new properties.
0944: *
0945: * @see org.millstone.base.data.Item#addProperty(Object, Property)
0946: */
0947: public boolean addItemProperty(Object id, Property property)
0948: throws UnsupportedOperationException {
0949: throw new UnsupportedOperationException(
0950: "Indexed container item "
0951: + "does not support adding new properties");
0952: }
0953:
0954: /**
0955: * Indexed container does not support removing properties.
0956: *
0957: * @see org.millstone.base.data.Item#removeProperty(Object)
0958: */
0959: public boolean removeItemProperty(Object id)
0960: throws UnsupportedOperationException {
0961: throw new UnsupportedOperationException(
0962: "Indexed container item does not support property removal");
0963: }
0964:
0965: }
0966:
0967: /*
0968: * A class implementing the org.millstone.base.data.Property interface to be
0969: * contained in the Items contained in the list. @author IT Mill Ltd.
0970: *
0971: * @version 3.1.1
0972: * @since 3.0
0973: */
0974: private class IndexedContainerProperty implements Property,
0975: Property.ValueChangeNotifier {
0976:
0977: /** ID of the Item, where the Property resides */
0978: private Object itemId;
0979:
0980: /** Id of the Property */
0981: private Object propertyId;
0982:
0983: /**
0984: * Constructs a new ListProperty object and connect it to a ListItem and
0985: * a ListContainer.
0986: *
0987: * @param itemId
0988: * ID of the Item to connect the new Property to
0989: * @param propertyId
0990: * Property ID of the new Property
0991: * @param host
0992: * the list that contains the Item to contain the new
0993: * Property
0994: */
0995: private IndexedContainerProperty(Object itemId,
0996: Object propertyId) {
0997: if (itemId == null || propertyId == null)
0998: throw new NullPointerException();
0999: this .propertyId = propertyId;
1000: this .itemId = itemId;
1001: }
1002:
1003: /**
1004: * Return the type of the Property. The methods <code>getValue</code>
1005: * and <code>setValue</code> must be compatible with this type: one
1006: * must be able to safely cast the value returned from
1007: * <code>getValue</code> to the given type and pass any variable
1008: * assignable to this type as a parameter to <code>setValue</code.
1009: *
1010: * @return type of the Property
1011: */
1012: public Class getType() {
1013: return (Class) types.get(propertyId);
1014: }
1015:
1016: /**
1017: * Gets the value stored in the Property.
1018: *
1019: * @return the value stored in the Property
1020: */
1021: public Object getValue() {
1022: return ((Hashtable) items.get(itemId)).get(propertyId);
1023: }
1024:
1025: /**
1026: * <p>
1027: * Test if the Property is in read-only mode. In read-only mode calls to
1028: * the method <code>setValue</code> will throw
1029: * <code>ReadOnlyException</code> s and will not modify the value of
1030: * the Property.
1031: * </p>
1032: *
1033: * @return <code>true</code> if the Property is in read-only mode,
1034: * <code>false</code> if it's not
1035: */
1036: public boolean isReadOnly() {
1037: return readOnlyProperties.contains(this );
1038: }
1039:
1040: /**
1041: * Set the Property's read-only mode to the specified status.
1042: *
1043: * @param newStatus
1044: * new read-only status of the Property
1045: */
1046: public void setReadOnly(boolean newStatus) {
1047: if (newStatus)
1048: readOnlyProperties.add(this );
1049: else
1050: readOnlyProperties.remove(this );
1051: }
1052:
1053: /**
1054: * Sets the value of the Property. By default this method will try to
1055: * assign the value directly, but if it is unassignable, it will try to
1056: * use the <code>String</code> constructor of the internal data type
1057: * to assign the value,
1058: *
1059: * @param newValue
1060: * New value of the Property. This should be assignable to
1061: * the Property's internal type or support
1062: * <code>toString</code>.
1063: *
1064: * @throws Property.ReadOnlyException
1065: * if the object is in read-only mode
1066: * @throws Property.ConversionException
1067: * if <code>newValue</code> can't be converted into the
1068: * Property's native type directly or through
1069: * <code>String</code>
1070: */
1071: public void setValue(Object newValue)
1072: throws Property.ReadOnlyException,
1073: Property.ConversionException {
1074:
1075: // Get the Property set
1076: Hashtable propertySet = (Hashtable) items.get(itemId);
1077:
1078: // Support null values on all types
1079: if (newValue == null)
1080: propertySet.remove(propertyId);
1081:
1082: // Try to assign the compatible value directly
1083: else if (getType().isAssignableFrom(newValue.getClass()))
1084: propertySet.put(propertyId, newValue);
1085:
1086: // Otherwise try to convert the value trough string constructor
1087: else
1088: try {
1089:
1090: // Get the string constructor
1091: Constructor constr = getType().getConstructor(
1092: new Class[] { String.class });
1093:
1094: // Create new object from the string
1095: propertySet.put(propertyId, constr
1096: .newInstance(new Object[] { newValue
1097: .toString() }));
1098:
1099: } catch (java.lang.Exception e) {
1100: throw new Property.ConversionException(
1101: "Conversion for value '" + newValue
1102: + "' of class "
1103: + newValue.getClass().getName()
1104: + " to " + getType().getName()
1105: + " failed");
1106: }
1107:
1108: firePropertyValueChange(this );
1109: }
1110:
1111: /**
1112: * Return the value of the Property in human readable textual format.
1113: * The return value should be assignable to the <code>setValue</code>
1114: * method if the Property is not in read-only mode.
1115: *
1116: * @return <code>String</code> representation of the value stored in
1117: * the Property
1118: */
1119: public String toString() {
1120: Object value = getValue();
1121: if (value == null)
1122: return null;
1123: return value.toString();
1124: }
1125:
1126: /**
1127: * Calculates a integer hash-code for the Property that's unique inside
1128: * the Item containing the Property. Two different Properties inside the
1129: * same Item contained in the same list always have different
1130: * hash-codes, though Properties in different Items may have identical
1131: * hash-codes.
1132: *
1133: * @return A locally unique hash-code as integer
1134: */
1135: public int hashCode() {
1136: return itemId.hashCode() ^ propertyId.hashCode()
1137: ^ IndexedContainer.this .hashCode();
1138: }
1139:
1140: /**
1141: * Tests if the given object is the same as the this object. Two
1142: * Properties got from an Item with the same ID are equal.
1143: *
1144: * @param obj
1145: * an object to compare with this object
1146: * @return <code>true</code> if the given object is the same as this
1147: * object, <code>false</code> if not
1148: */
1149: public boolean equals(Object obj) {
1150: if (obj == null
1151: || !obj.getClass().equals(
1152: IndexedContainerProperty.class))
1153: return false;
1154: IndexedContainerProperty lp = (IndexedContainerProperty) obj;
1155: return lp.getHost() == getHost()
1156: && lp.propertyId.equals(propertyId)
1157: && lp.itemId.equals(itemId);
1158: }
1159:
1160: /**
1161: * Registers a new value change listener for this Property.
1162: *
1163: * @param listener
1164: * the new Listener to be registered
1165: * @see org.millstone.base.data.Property.ValueChangeNotifier#addListener(ValueChangeListener)
1166: */
1167: public void addListener(Property.ValueChangeListener listener) {
1168: addSinglePropertyChangeListener(propertyId, itemId,
1169: listener);
1170: }
1171:
1172: /**
1173: * Removes a previously registered value change listener.
1174: *
1175: * @param listener
1176: * listener to be removed
1177: * @see org.millstone.base.data.Property.ValueChangeNotifier#removeListener(ValueChangeListener)
1178: */
1179: public void removeListener(Property.ValueChangeListener listener) {
1180: removeSinglePropertyChangeListener(propertyId, itemId,
1181: listener);
1182: }
1183:
1184: private IndexedContainer getHost() {
1185: return IndexedContainer.this ;
1186: }
1187:
1188: }
1189:
1190: /*
1191: * (non-Javadoc)
1192: *
1193: * @see org.millstone.base.data.Container.Sortable#sort(java.lang.Object[],
1194: * boolean[])
1195: */
1196: public synchronized void sort(Object[] propertyId,
1197: boolean[] ascending) {
1198:
1199: // Remove any non-sortable property ids
1200: ArrayList ids = new ArrayList();
1201: ArrayList orders = new ArrayList();
1202: Collection sortable = getSortableContainerPropertyIds();
1203: for (int i = 0; i < propertyId.length; i++)
1204: if (sortable.contains(propertyId[i])) {
1205: ids.add(propertyId[i]);
1206: orders.add(new Boolean(
1207: i < ascending.length ? ascending[i] : true));
1208: }
1209:
1210: if (ids.size() == 0)
1211: return;
1212: sortPropertyId = ids.toArray();
1213: sortDirection = new boolean[orders.size()];
1214: for (int i = 0; i < sortDirection.length; i++)
1215: sortDirection[i] = ((Boolean) orders.get(i)).booleanValue();
1216:
1217: // Sort
1218: Collections.sort(this .itemIds, this );
1219: fireContentsChange();
1220:
1221: // Remove temporary references
1222: sortPropertyId = null;
1223: sortDirection = null;
1224:
1225: }
1226:
1227: /*
1228: * (non-Javadoc)
1229: *
1230: * @see org.millstone.base.data.Container.Sortable#getSortableContainerPropertyIds()
1231: */
1232: public Collection getSortableContainerPropertyIds() {
1233:
1234: LinkedList list = new LinkedList();
1235: for (Iterator i = this .propertyIds.iterator(); i.hasNext();) {
1236: Object id = i.next();
1237: Class type = this .getType(id);
1238: if (type != null && Comparable.class.isAssignableFrom(type)) {
1239: list.add(id);
1240: }
1241: }
1242:
1243: return list;
1244: }
1245:
1246: /**
1247: * Compare two items for sorting.
1248: *
1249: * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
1250: * @see #sort((java.lang.Object[], boolean[])
1251: */
1252: public int compare(Object o1, Object o2) {
1253:
1254: for (int i = 0; i < sortPropertyId.length; i++) {
1255:
1256: // Get the compared properties
1257: Property pp1 = getContainerProperty(o1,
1258: this .sortPropertyId[i]);
1259: Property pp2 = getContainerProperty(o2,
1260: this .sortPropertyId[i]);
1261:
1262: // Get the compared values
1263: Object p1 = pp1 == null ? null : pp1.getValue();
1264: Object p2 = pp2 == null ? null : pp2.getValue();
1265:
1266: // Result of the comparison
1267: int r = 0;
1268:
1269: // Normal non-null comparison
1270: if (p1 != null && p2 != null) {
1271: if ((p1 instanceof Boolean) && (p2 instanceof Boolean))
1272: r = p1.equals(p2) ? 0 : ((this .sortDirection[i] ? 1
1273: : -1) * (((Boolean) p1).booleanValue() ? 1
1274: : -1));
1275: else
1276: r = this .sortDirection[i] ? ((Comparable) p1)
1277: .compareTo(p2) : -((Comparable) p1)
1278: .compareTo(p2);
1279: }
1280:
1281: // If both are nulls
1282: else if (p1 == p2)
1283: r = 0;
1284:
1285: // If one of the properties are null
1286: else
1287: r = p1 == null ? -1 : 1;
1288:
1289: // If order can be decided
1290: if (r != 0)
1291: return r;
1292: }
1293:
1294: return 0;
1295: }
1296:
1297: /* Support cloning of the IndexedContainer cleanly */
1298: public Object clone() throws CloneNotSupportedException {
1299:
1300: // Create the clone
1301: IndexedContainer nc = new IndexedContainer();
1302:
1303: // Clone the shallow properties
1304: nc.itemIds = this .itemIds != null ? (ArrayList) this .itemIds
1305: .clone() : null;
1306: nc.itemSetChangeListeners = this .itemSetChangeListeners != null ? (LinkedList) this .itemSetChangeListeners
1307: .clone()
1308: : null;
1309: nc.propertyIds = this .propertyIds != null ? (ArrayList) this .propertyIds
1310: .clone()
1311: : null;
1312: nc.propertySetChangeListeners = this .propertySetChangeListeners != null ? (LinkedList) this .propertySetChangeListeners
1313: .clone()
1314: : null;
1315: nc.propertyValueChangeListeners = this .propertyValueChangeListeners != null ? (LinkedList) this .propertyValueChangeListeners
1316: .clone()
1317: : null;
1318: nc.readOnlyProperties = this .readOnlyProperties != null ? (HashSet) this .readOnlyProperties
1319: .clone()
1320: : null;
1321: nc.singlePropertyValueChangeListeners = this .singlePropertyValueChangeListeners != null ? (Hashtable) this .singlePropertyValueChangeListeners
1322: .clone()
1323: : null;
1324: nc.sortDirection = this .sortDirection != null ? (boolean[]) this .sortDirection
1325: .clone()
1326: : null;
1327: nc.sortPropertyId = this .sortPropertyId != null ? (Object[]) this .sortPropertyId
1328: .clone()
1329: : null;
1330: nc.types = this .types != null ? (Hashtable) this .types.clone()
1331: : null;
1332:
1333: // Clone property-values
1334: if (this .items == null)
1335: nc.items = null;
1336: else {
1337: nc.items = new Hashtable();
1338: for (Iterator i = this .items.keySet().iterator(); i
1339: .hasNext();) {
1340: Object id = i.next();
1341: Hashtable it = (Hashtable) this .items.get(id);
1342: nc.items.put(id, it.clone());
1343: }
1344: }
1345:
1346: return nc;
1347: }
1348:
1349: public boolean equals(Object obj) {
1350:
1351: // Only ones of the objects of the same class can be equal
1352: if (!(obj instanceof IndexedContainer))
1353: return false;
1354: IndexedContainer o = (IndexedContainer) obj;
1355:
1356: // Check the properties one by one
1357: if (itemIds != o.itemIds && o.itemIds != null
1358: && !o.itemIds.equals(this .itemIds))
1359: return false;
1360: if (items != o.items && o.items != null
1361: && !o.items.equals(this .items))
1362: return false;
1363: if (itemSetChangeListeners != o.itemSetChangeListeners
1364: && o.itemSetChangeListeners != null
1365: && !o.itemSetChangeListeners
1366: .equals(this .itemSetChangeListeners))
1367: return false;
1368: if (propertyIds != o.propertyIds && o.propertyIds != null
1369: && !o.propertyIds.equals(this .propertyIds))
1370: return false;
1371: if (propertySetChangeListeners != o.propertySetChangeListeners
1372: && o.propertySetChangeListeners != null
1373: && !o.propertySetChangeListeners
1374: .equals(this .propertySetChangeListeners))
1375: return false;
1376: if (propertyValueChangeListeners != o.propertyValueChangeListeners
1377: && o.propertyValueChangeListeners != null
1378: && !o.propertyValueChangeListeners
1379: .equals(this .propertyValueChangeListeners))
1380: return false;
1381: if (readOnlyProperties != o.readOnlyProperties
1382: && o.readOnlyProperties != null
1383: && !o.readOnlyProperties
1384: .equals(this .readOnlyProperties))
1385: return false;
1386: if (singlePropertyValueChangeListeners != o.singlePropertyValueChangeListeners
1387: && o.singlePropertyValueChangeListeners != null
1388: && !o.singlePropertyValueChangeListeners
1389: .equals(this .singlePropertyValueChangeListeners))
1390: return false;
1391: if (sortDirection != o.sortDirection && o.sortDirection != null
1392: && !o.sortDirection.equals(this .sortDirection))
1393: return false;
1394: if (sortPropertyId != o.sortPropertyId
1395: && o.sortPropertyId != null
1396: && !o.sortPropertyId.equals(this .sortPropertyId))
1397: return false;
1398: if (types != o.types && o.types != null
1399: && !o.types.equals(this .types))
1400: return false;
1401:
1402: return true;
1403: }
1404:
1405: public int hashCode() {
1406:
1407: // The hash-code is calculated as combination hash of the members
1408: return (this .itemIds != null ? this .itemIds.hashCode() : 0)
1409: ^ (this .items != null ? this .items.hashCode() : 0)
1410: ^ (this .itemSetChangeListeners != null ? this .itemSetChangeListeners
1411: .hashCode()
1412: : 0)
1413: ^ (this .propertyIds != null ? this .propertyIds
1414: .hashCode() : 0)
1415: ^ (this .propertySetChangeListeners != null ? this .propertySetChangeListeners
1416: .hashCode()
1417: : 0)
1418: ^ (this .propertyValueChangeListeners != null ? this .propertyValueChangeListeners
1419: .hashCode()
1420: : 0)
1421: ^ (this .readOnlyProperties != null ? this .readOnlyProperties
1422: .hashCode()
1423: : 0)
1424: ^ (this .singlePropertyValueChangeListeners != null ? this .singlePropertyValueChangeListeners
1425: .hashCode()
1426: : 0)
1427: ^ (this .sortPropertyId != null ? this .sortPropertyId
1428: .hashCode() : 0)
1429: ^ (this .types != null ? this .types.hashCode() : 0)
1430: ^ (this .sortDirection != null ? this .sortDirection
1431: .hashCode() : 0);
1432: }
1433:
1434: }
|