/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Michael Powers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.ui; import java.util.Enumeration; import net.wotonomy.control.EODelayedObserver; import net.wotonomy.control.EOObserverCenter; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.foundation.NSMutableDictionary; /** * Associations observe DisplayGroups and associate a user interface component * with one or more keys on the objects in the display group.
*
* * Associations are created with a ui component in the constructor. Then, one or * more aspects are bound to display groups and/or property keys with the * bindAspect() method. Finally, the association is initialized with the * establishConnection() method.
*
* * Per the openstep convention, you do not need to retain a reference to the * association after it is created; the association will be garbage-collected * when the ui component is garbage-collected.
*
* * (Because java components don't have delegates like openstep components do, * java-based associations will likely need to implement some sort of listener * and add itself to the component's list of listeners so that the component * will have a strong reference (and the only reference) to the association.) *
*
* * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 904 $ */ public class EOAssociation extends EODelayedObserver { // aspect constants public static final String ActionAspect = "action"; public static final String EnabledAspect = "enabled"; public static final String SourceAspect = "source"; public static final String ArgumentAspect = "argument"; public static final String ParentAspect = "parent"; public static final String TitlesAspect = "titles"; public static final String BoldAspect = "bold"; public static final String SelectedObjectAspect = "selectedObject"; public static final String ValueAspect = "value"; public static final String DestinationAspect = "destination"; public static final String SelectedTitleAspect = "selectedTitle"; public static final String URLAspect = "URL"; public static final String ItalicAspect = "italic"; public static final String ChildrenAspect = "children"; // not in spec public static final String IsLeafAspect = "isLeaf"; // not in spec public static final String EditableAspect = "editable"; // not in spec public static final String VisibleAspect = "visible"; // not in spec public static final String ObjectsAspect = "objects"; // not in spec public static final String LabelAspect = "label"; // not in spec public static final String IconAspect = "icon"; // not in spec public static final String AttributeAspectSignature = "A"; public static final String NullAspectSignature = ""; public static final String AttributeToOneAspectSignature = "A1"; public static final String ToOneAspectSignature = "1"; public static final String AttributeToOneToManyAspectSignature = "A1M"; public static final String ToOneToManyAspectSignature = "1M"; public static final String AttributeToManyAspectSignature = "AM"; public static final String ToManyAspectSignature = "M"; protected Object control; protected NSMutableDictionary aspectToGroup; protected NSMutableDictionary aspectToKey; /** * Default constructor. */ public EOAssociation() { aspectToGroup = new NSMutableDictionary(); aspectToKey = new NSMutableDictionary(); control = null; } /** * Constructor specifying the object to be controlled by this association. Does * not establish connection. */ public EOAssociation(Object anObject) { this(); control = anObject; } /** * Returns a List of aspect signatures whose contents correspond with the * aspects list. Each element is a string whose characters represent a * capability of the corresponding aspect. * * An empty signature "" means that the aspect can bind without needing a key. * This implementation returns "A1M" for each element in the aspects array. */ public static NSArray aspectSignatures() { int size = aspects().count(); NSMutableArray result = new NSMutableArray(); for (int i = 0; i < size; i++) { result.addObject(AttributeToOneToManyAspectSignature); } return result; } /** * Returns a List that describes the aspects supported by this class. Each * element in the list is the string name of the aspect. This implementation * returns an empty list. * * TODO: Is this static in the WebObjects published API? If not, fix. */ public static NSArray aspects() { return new NSArray(); } /** * Returns all registered subclasses of EOAssociation for which usableWithObject * with the specified object returns true. You should only call this method on * the EOAssociation class. */ public static NSArray associationClassesForObject(Object anObject) { throw new RuntimeException("Not implemented yet."); } /** * Returns a List of EOAssociation subclasses that, for the objects that are * usable for this association, are less suitable than this association. This * implementation returns an empty list. */ public static NSArray associationClassesSuperseded() { return new NSArray(); } /** * Binds the specified aspect of this association to the specified key on the * specified display group. */ public void bindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { // unattach old group, if any EODisplayGroup oldGroup = displayGroupForAspect(anAspect); if (oldGroup != null) { EOObserverCenter.removeObserver(this, oldGroup); } // attach new group if (aDisplayGroup != null) { aspectToGroup.setObjectForKey(aDisplayGroup, anAspect); } else { aspectToGroup.removeObjectForKey(anAspect); } // attach new key if (aKey != null) { aspectToKey.setObjectForKey(aKey, anAspect); } else { aspectToKey.removeObjectForKey(anAspect); } } /** * Breaks the connection between this association and its object. Override to * stop listening for events from the object, but remember to call this method. * This implementation unregisters this association from observing each bound * display group. */ public void breakConnection() { discardPendingNotification(); Enumeration e = aspectToGroup.objectEnumerator(); while (e.hasMoreElements()) { EOObserverCenter.removeObserver(this, e.nextElement()); } } /** * Returns whether this association can bind to the specified display group on * the specified key for the specified aspect. This implementation returns * false. */ public boolean canBindAspect(String anAspect, EODisplayGroup aDisplayGroup, String aKey) { return false; } /** * Copies the binding for each aspect in this association that has a matching * aspect in the specified association. */ public void copyMatchingBindingsFromAssociation(EOAssociation anAssociation) { // FIXME: this is broken: aspects() returns EOAssociation.aspects() // NOTE: This is actually quite crazy - should this even be static? -ceg NSMutableArray ourAspects = new NSMutableArray(this.aspects()); NSArray theirAspects = anAssociation.aspects(); String aspect; Enumeration e = ourAspects.objectEnumerator(); while (e.hasMoreElements()) { aspect = e.nextElement().toString(); if (theirAspects.containsObject(aspect)) { this.bindAspect(aspect, anAssociation.displayGroupForAspect(aspect), anAssociation.displayGroupKeyForAspect(aspect)); } } } /** * Returns the display group that is bound the specified aspect, or null if no * display group is currently bound to that aspect. */ public EODisplayGroup displayGroupForAspect(String anAspect) { return (EODisplayGroup) aspectToGroup.objectForKey(anAspect); } /** * Returns the key for the display group bound to the specified aspect, or null * if no display group is currently bound to that aspect. */ public String displayGroupKeyForAspect(String anAspect) { return (String) aspectToKey.objectForKey(anAspect); } /** * The human-readable descriptive name for this association. This implementation * returns the class name. */ public String displayName() // was static - can static method get class name? { String className = getClass().getName(); int index = className.lastIndexOf("."); if (index == -1) return className; return className.substring(index + 1); } /** * Forces this association to cause the object to stop editing and validate the * user's input. This implementation returns true. * * @return false if there were problems validating, or true to continue. */ public boolean endEditing() { return true; } /** * Establishes a connection between this association and the controlled object. * Subclasses should populate their controlled object from the display group and * begin listening for events from their controlled object here, but remember to * call this method. Any existing value in the controlled object will be * overwritten. This implementation registers this assocation to observer * changes to each of the bound display groups. */ public void establishConnection() { Enumeration e = aspectToGroup.objectEnumerator(); while (e.hasMoreElements()) { EOObserverCenter.addObserver(this, e.nextElement()); } } /** * Returns whether this class can control the specified object. This * implementation returns false. */ public static boolean isUsableWithObject(Object anObject) { return false; } /** * Returns the object that is currently controlled by this association, or null * if no object is currently controlled. */ public Object object() { return control; } /** * Returns a List of properties of the controlled object that are controlled by * this class. For example, "stringValue", or "selected". This implementation * returns an empty list. */ public static NSArray objectKeysTaken() { return new NSArray(); } /** * Returns the aspect that is considered primary or default. This is typically * "value" or somesuch. This implementation returns null. */ public static String primaryAspect() { return null; } /** * Writes the specified value for the display group and key currently bound to * the specified aspect. This implementation calls * EODisplayGroup.setSelectedObjectValue(). * * @return True if the value was successfully set, otherwise false. */ public boolean setValueForAspect(Object aValue, String anAspect) { EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); if (group == null) return false; String key = (String) aspectToKey.objectForKey(anAspect); if (key == null) return false; // FIXME: is this the right method to call return group.setSelectedObjectValue(aValue, key); } /** * Writes the specified value for the display group and key currently bound to * the specified aspect, at the specified index (assuming it's an indexed * property). This implementation calls * EODisplayGroup.setValueForObjectAtIndex(). * * @return True if the value was successfully set, otherwise false. */ public boolean setValueForAspectAtIndex(Object aValue, String anAspect, int anIndex) { EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); if (group == null) return false; String key = (String) aspectToKey.objectForKey(anAspect); if (key == null) return false; // FIXME: is this the right method to call? return group.setValueForObjectAtIndex(aValue, anIndex, key); } /** * Called by subclasses to notify that the user's input failed validation. * Notifies the display group for the aspect, returning the result. This * implementation calls EODisplayGroup.associationFailedToValidateValue() with * the display group's selected object. * * @return True if the user should be allowed to continue, meaning that the * display group with handle user notification, otherwise false meaning * the caller should notify the user. */ public boolean shouldEndEditing(String anAspect, String anInvalidInput, String anErrorDescription) { EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); if (group == null) return false; String key = (String) aspectToKey.objectForKey(anAspect); if (key == null) return false; return group.associationFailedToValidateValue(this, anInvalidInput, key, group.selectedObject(), // FIXME: is // this // correct? anErrorDescription); } /** * Called by subclasses to notify that the user's input failed validation. * Notifies the display group for the aspect, returning the result. This * implementation calls EODisplayGroup.associationFailedToValidateValue() with * the object in the display group's displayed objects array at the specified * index. * * @return True if the user should be allowed to continue, meaning that the * display group with handle user notification, otherwise false meaning * the caller should notify the user. */ public boolean shouldEndEditingAtIndex(String anAspect, String anInvalidInput, String anErrorDescription, int anIndex) { EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); if (group == null) return false; String key = (String) aspectToKey.objectForKey(anAspect); if (key == null) return false; return group.associationFailedToValidateValue(this, anInvalidInput, key, group.displayedObjects().objectAtIndex(anIndex), // FIXME: is this correct? anErrorDescription); } /** * Called when either the selection or the contents of an associated display * group have changed. This implementation does nothing. */ public void subjectChanged() { // does nothing } /** * Returns the current value for the display group and key associated with the * specified aspect. */ public Object valueForAspect(String anAspect) { EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); if (group == null) return null; String key = (String) aspectToKey.objectForKey(anAspect); if (key == null) return null; // FIXME: is this correct? use selected object? return group.valueForObject(group.selectedObject(), key); } /** * Returns the current value for the display group and key associated with the * specified aspect, and the specified index (assuming multiple values). */ public Object valueForAspectAtIndex(String anAspect, int anIndex) { EODisplayGroup group = (EODisplayGroup) aspectToGroup.objectForKey(anAspect); if (group == null) return null; String key = (String) aspectToKey.objectForKey(anAspect); if (key == null) return null; // FIXME: is this the right method to call? return group.valueForObjectAtIndex(anIndex, key); } } /* * $Log$ Revision 1.2 2006/02/18 23:14:35 cgruber Update imports and maven * dependencies. * * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.7 2005/05/11 15:21:53 cgruber Change enum to enumeration, since * enum is now a keyword as of Java 5.0 * * A few other comments in the code. * * Revision 1.6 2003/08/06 23:07:52 chochos general code cleanup (mostly, * removing unused imports) * * Revision 1.5 2003/06/06 20:29:45 mpowers Now dequeuing the association when * connection is broken. Coalesced change events had been processed even after * breaking connection. * * Revision 1.4 2001/03/06 23:43:46 mpowers Implemented icon aspect for text * association. * * Revision 1.3 2001/02/17 16:52:05 mpowers Changes in imports to support * building with jdk1.1 collections. * * Revision 1.2 2001/01/25 17:46:11 mpowers Clarified usage and gc expectations. * * Revision 1.1.1.1 2000/12/21 15:48:07 mpowers Contributing wotonomy. * * Revision 1.11 2000/12/20 16:25:39 michael Added log to all files. * * */