/*
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.
* - "A" attribute: the aspect can be bound to
* an attribute.
* - "1" to-one: the aspect can be bound to a
* property that returns a single object.
* - "M" to-one: the aspect can be bound to a
* property that returns multiple objects.
*
* 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.
*
*
*/