/* 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. * * */