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