Coverage Report - net.wotonomy.ui.EOAssociation
 
Classes in this File Line Coverage Branch Coverage Complexity
EOAssociation
0% 
0% 
2.346
 
 1  
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2000 Michael Powers
 4  
 
 5  
 This library is free software; you can redistribute it and/or
 6  
 modify it under the terms of the GNU Lesser General Public
 7  
 License as published by the Free Software Foundation; either
 8  
 version 2.1 of the License, or (at your option) any later version.
 9  
 
 10  
 This library is distributed in the hope that it will be useful,
 11  
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 Lesser General Public License for more details.
 14  
 
 15  
 You should have received a copy of the GNU Lesser General Public
 16  
 License along with this library; if not, see http://www.gnu.org
 17  
 */
 18  
 
 19  
 package net.wotonomy.ui;
 20  
 
 21  
 import java.util.Enumeration;
 22  
 
 23  
 import net.wotonomy.control.EODelayedObserver;
 24  
 import net.wotonomy.control.EOObserverCenter;
 25  
 import net.wotonomy.foundation.NSArray;
 26  
 import net.wotonomy.foundation.NSMutableArray;
 27  
 import net.wotonomy.foundation.NSMutableDictionary;
 28  
 
 29  
 /**
 30  
 * Associations observe DisplayGroups and associate
 31  
 * a user interface component with one or more keys 
 32  
 * on the objects in the display group. <br><br>
 33  
 *
 34  
 * Associations are created with a ui component in
 35  
 * the constructor.  Then, one or more aspects are
 36  
 * bound to display groups and/or property keys with
 37  
 * the bindAspect() method.   Finally, the association
 38  
 * is initialized with the establishConnection() 
 39  
 * method. <br><br>
 40  
 *
 41  
 * Per the openstep convention, you do not need to 
 42  
 * retain a reference to the association after it is 
 43  
 * created; the association will be garbage-collected
 44  
 * when the ui component is garbage-collected. <br><br> 
 45  
 *
 46  
 * (Because java components don't have delegates like
 47  
 * openstep components do, java-based associations 
 48  
 * will likely need to implement some sort of listener
 49  
 * and add itself to the component's list of listeners
 50  
 * so that the component will have a strong reference
 51  
 * (and the only reference) to the association.) <br><br>
 52  
 *
 53  
 * @author michael@mpowers.net
 54  
 * @author $Author: cgruber $
 55  
 * @version $Revision: 904 $
 56  
 */
 57  
 public class EOAssociation extends EODelayedObserver
 58  
 {
 59  
         // aspect constants 
 60  
         public static final String ActionAspect = "action";
 61  
         public static final String EnabledAspect = "enabled";
 62  
         public static final String SourceAspect = "source";
 63  
         public static final String ArgumentAspect = "argument";
 64  
         public static final String ParentAspect = "parent";
 65  
         public static final String TitlesAspect = "titles";
 66  
         public static final String BoldAspect = "bold";
 67  
         public static final String SelectedObjectAspect = "selectedObject";
 68  
         public static final String ValueAspect = "value";
 69  
         public static final String DestinationAspect = "destination";
 70  
         public static final String SelectedTitleAspect = "selectedTitle";
 71  
         public static final String URLAspect = "URL";
 72  
         public static final String ItalicAspect = "italic";
 73  
         public static final String ChildrenAspect = "children"; // not in spec
 74  
         public static final String IsLeafAspect = "isLeaf"; // not in spec
 75  
         public static final String EditableAspect = "editable"; // not in spec
 76  
         public static final String VisibleAspect = "visible"; // not in spec
 77  
         public static final String ObjectsAspect = "objects"; // not in spec
 78  
         public static final String LabelAspect = "label"; // not in spec
 79  
         public static final String IconAspect = "icon"; // not in spec
 80  
                 
 81  
         public static final String AttributeAspectSignature = "A";
 82  
         public static final String NullAspectSignature = "";
 83  
         public static final String AttributeToOneAspectSignature = "A1"; 
 84  
         public static final String ToOneAspectSignature = "1";
 85  
         public static final String AttributeToOneToManyAspectSignature = "A1M"; 
 86  
         public static final String ToOneToManyAspectSignature = "1M";
 87  
         public static final String AttributeToManyAspectSignature = "AM";
 88  
         public static final String ToManyAspectSignature = "M";
 89  
                 
 90  
     protected Object control;
 91  
     protected NSMutableDictionary aspectToGroup;
 92  
     protected NSMutableDictionary aspectToKey;
 93  
 
 94  
     /**
 95  
     * Default constructor.
 96  
     */ 
 97  0
     public EOAssociation ()
 98  0
     {
 99  0
         aspectToGroup = new NSMutableDictionary();
 100  0
         aspectToKey = new NSMutableDictionary();
 101  0
         control = null;
 102  0
     }
 103  
     
 104  
     /**
 105  
     * Constructor specifying the object to be controlled by this
 106  
     * association.  Does not establish connection.
 107  
     */
 108  
     public EOAssociation ( Object anObject )
 109  
     {
 110  0
         this();
 111  0
         control = anObject;
 112  0
     }
 113  
     
 114  
     /**
 115  
     * Returns a List of aspect signatures whose contents
 116  
     * correspond with the aspects list.  Each element is 
 117  
     * a string whose characters represent a capability of
 118  
     * the corresponding aspect. <ul>
 119  
     * <li>"A" attribute: the aspect can be bound to
 120  
     * an attribute.</li>
 121  
     * <li>"1" to-one: the aspect can be bound to a
 122  
     * property that returns a single object.</li>
 123  
     * <li>"M" to-one: the aspect can be bound to a
 124  
     * property that returns multiple objects.</li>
 125  
     * </ul> 
 126  
     * An empty signature "" means that the aspect can
 127  
     * bind without needing a key.
 128  
     * This implementation returns "A1M" for each
 129  
     * element in the aspects array.
 130  
     */
 131  
     public static NSArray aspectSignatures ()
 132  
     {
 133  0
         int size = aspects().count();
 134  0
         NSMutableArray result = new NSMutableArray();
 135  0
         for ( int i = 0; i < size; i++ )
 136  
         {
 137  0
             result.addObject( AttributeToOneToManyAspectSignature );
 138  
         }
 139  0
         return result;
 140  
     }
 141  
     
 142  
     /**
 143  
     * Returns a List that describes the aspects supported
 144  
     * by this class.  Each element in the list is the string
 145  
     * name of the aspect.  This implementation returns an
 146  
     * empty list.
 147  
     * 
 148  
     * TODO: Is this static in the WebObjects published API? If not, fix.
 149  
     */
 150  
     public static NSArray aspects ()
 151  
     {
 152  0
         return new NSArray();
 153  
     }
 154  
     
 155  
     /**
 156  
     * Returns all registered subclasses of EOAssociation 
 157  
     * for which usableWithObject with the specified object
 158  
     * returns true.  You should only call this method on 
 159  
     * the EOAssociation class.
 160  
     */
 161  
     public static NSArray associationClassesForObject ( 
 162  
         Object anObject )
 163  
     {
 164  0
         throw new RuntimeException( "Not implemented yet." );
 165  
     }
 166  
     
 167  
     /**
 168  
     * Returns a List of EOAssociation subclasses that,
 169  
     * for the objects that are usable for this association,
 170  
     * are less suitable than this association.
 171  
     * This implementation returns an empty list.
 172  
     */
 173  
     public static NSArray associationClassesSuperseded ()
 174  
     {
 175  0
         return new NSArray();
 176  
     }
 177  
     
 178  
 
 179  
     /**
 180  
     * Binds the specified aspect of this association to the
 181  
     * specified key on the specified display group.
 182  
     */
 183  
     public void bindAspect ( 
 184  
         String anAspect, EODisplayGroup aDisplayGroup, String aKey )
 185  
     {
 186  
                 // unattach old group, if any
 187  0
                 EODisplayGroup oldGroup = displayGroupForAspect( anAspect );
 188  0
                 if ( oldGroup != null )
 189  
                 {
 190  0
                         EOObserverCenter.removeObserver( this, oldGroup );        
 191  
                 }
 192  
 
 193  
                 // attach new group
 194  0
                 if ( aDisplayGroup != null )
 195  
                 {
 196  0
                         aspectToGroup.setObjectForKey( aDisplayGroup, anAspect );
 197  0
                 }
 198  
                 else
 199  
                 {
 200  0
                         aspectToGroup.removeObjectForKey( anAspect );        
 201  
                 }
 202  
                 // attach new key
 203  0
                 if ( aKey != null )
 204  
                 {
 205  0
                         aspectToKey.setObjectForKey( aKey, anAspect );
 206  0
                 }
 207  
                 else
 208  
                 {
 209  0
                         aspectToKey.removeObjectForKey( anAspect );
 210  
                 }
 211  0
     }
 212  
     
 213  
     /**
 214  
     * Breaks the connection between this association and 
 215  
     * its object.  Override to stop listening for events
 216  
     * from the object, but remember to call this method.
 217  
     * This implementation unregisters this association
 218  
         * from observing each bound display group.
 219  
     */
 220  
     public void breakConnection ()
 221  
     {
 222  0
         discardPendingNotification();
 223  0
         Enumeration e = aspectToGroup.objectEnumerator();
 224  0
                 while ( e.hasMoreElements() )
 225  
                 {
 226  0
                         EOObserverCenter.removeObserver( this, e.nextElement() );        
 227  0
                 }
 228  0
     }
 229  
 
 230  
     /**
 231  
     * Returns whether this association can bind to the
 232  
     * specified display group on the specified key for
 233  
     * the specified aspect.  
 234  
     * This implementation returns false.
 235  
     */
 236  
     public boolean canBindAspect ( 
 237  
         String anAspect, EODisplayGroup aDisplayGroup, String aKey)
 238  
     {
 239  0
         return false;
 240  
     }
 241  
     
 242  
     /**
 243  
     * Copies the binding for each aspect in this association
 244  
     * that has a matching aspect in the specified association.
 245  
     */
 246  
     public void copyMatchingBindingsFromAssociation ( 
 247  
         EOAssociation anAssociation )
 248  
     {
 249  
                 //FIXME: this is broken: aspects() returns EOAssociation.aspects()
 250  
                 //NOTE: This is actually quite crazy - should this even be static? -ceg
 251  0
         NSMutableArray ourAspects = new NSMutableArray( this.aspects() );
 252  0
         NSArray theirAspects = anAssociation.aspects();
 253  
 
 254  
         String aspect;
 255  0
         Enumeration e = ourAspects.objectEnumerator();
 256  0
         while ( e.hasMoreElements() )
 257  
         {
 258  0
             aspect = e.nextElement().toString();
 259  0
             if ( theirAspects.containsObject( aspect ) )
 260  
             {
 261  0
                 this.bindAspect(
 262  0
                 aspect,
 263  0
                 anAssociation.displayGroupForAspect( aspect ),
 264  0
                 anAssociation.displayGroupKeyForAspect( aspect ) );
 265  0
             }
 266  
         }
 267  0
     }
 268  
     
 269  
     /**
 270  
     * Returns the display group that is bound the specified
 271  
     * aspect, or null if no display group is currently 
 272  
     * bound to that aspect.
 273  
     */
 274  
     public EODisplayGroup displayGroupForAspect ( String anAspect )
 275  
     {
 276  0
         return (EODisplayGroup) aspectToGroup.objectForKey( anAspect );
 277  
     }
 278  
     
 279  
     /**
 280  
     * Returns the key for the display group bound to the
 281  
     * specified aspect, or null if no display group is currently
 282  
     * bound to that aspect.
 283  
     */
 284  
     public String displayGroupKeyForAspect ( String anAspect )
 285  
     {
 286  0
         return (String) aspectToKey.objectForKey( anAspect );
 287  
     }
 288  
     
 289  
     /**
 290  
     * The human-readable descriptive name for this association.
 291  
     * This implementation returns the class name.
 292  
     */
 293  
     public String displayName () // was static - can static method get class name?
 294  
     {
 295  0
             String className = getClass().getName();
 296  0
             int index = className.lastIndexOf( "." );
 297  0
             if ( index == -1 ) return className;
 298  0
             return className.substring( index+1 );
 299  
     }
 300  
 
 301  
     /**
 302  
     * Forces this association to cause the object to 
 303  
     * stop editing and validate the user's input.
 304  
     * This implementation returns true.
 305  
     * @return false if there were problems validating,
 306  
     * or true to continue.
 307  
     */
 308  
     public boolean endEditing ()
 309  
     {
 310  0
         return true;
 311  
     }
 312  
 
 313  
     /**
 314  
     * Establishes a connection between this association
 315  
     * and the controlled object.  Subclasses should populate
 316  
         * their controlled object from the display group and begin
 317  
     * listening for events from their controlled object here,
 318  
         * but remember to call this method.  Any existing value
 319  
         * in the controlled object will be overwritten.
 320  
     * This implementation registers this assocation to 
 321  
         * observer changes to each of the bound display groups.
 322  
     */
 323  
     public void establishConnection ()
 324  
     {
 325  0
         Enumeration e = aspectToGroup.objectEnumerator();
 326  0
                 while ( e.hasMoreElements() )
 327  
                 {
 328  0
                         EOObserverCenter.addObserver( this, e.nextElement() );        
 329  0
                 }
 330  0
     }
 331  
     
 332  
     /**
 333  
     * Returns whether this class can control the specified 
 334  
     * object.  This implementation returns false.
 335  
     */
 336  
     public static boolean isUsableWithObject ( Object anObject )
 337  
     {
 338  0
         return false;
 339  
     }
 340  
     
 341  
     /**
 342  
     * Returns the object that is currently controlled by this
 343  
     * association, or null if no object is currently controlled.
 344  
     */
 345  
     public Object object ()
 346  
     {
 347  0
         return control;
 348  
     }
 349  
     
 350  
     /**
 351  
     * Returns a List of properties of the controlled object
 352  
     * that are controlled by this class.  For example,
 353  
     * "stringValue", or "selected".
 354  
     * This implementation returns an empty list.
 355  
     */
 356  
     public static NSArray objectKeysTaken ()
 357  
     {
 358  0
         return new NSArray();
 359  
     }
 360  
     
 361  
     /**
 362  
     * Returns the aspect that is considered primary
 363  
     * or default.  This is typically "value" or somesuch.
 364  
     * This implementation returns null.
 365  
     */
 366  
     public static String primaryAspect ()
 367  
     {
 368  0
         return null;
 369  
     }
 370  
     
 371  
     /**
 372  
     * Writes the specified value for the display group
 373  
     * and key currently bound to the specified aspect.
 374  
     * This implementation calls 
 375  
     * EODisplayGroup.setSelectedObjectValue().
 376  
     * @return True if the value was successfully set,
 377  
     * otherwise false.
 378  
     */
 379  
     public boolean setValueForAspect ( 
 380  
         Object aValue,
 381  
         String anAspect )
 382  
     {
 383  0
         EODisplayGroup group = (EODisplayGroup)
 384  0
             aspectToGroup.objectForKey( anAspect );
 385  0
         if ( group == null ) return false;
 386  0
         String key = (String)
 387  0
             aspectToKey.objectForKey( anAspect );
 388  0
         if ( key == null ) return false;
 389  
         
 390  
         //FIXME: is this the right method to call
 391  0
         return group.setSelectedObjectValue( aValue, key );
 392  
     }
 393  
     
 394  
 
 395  
     /**
 396  
     * Writes the specified value for the display group
 397  
     * and key currently bound to the specified aspect,
 398  
     * at the specified index (assuming it's an indexed
 399  
     * property).
 400  
     * This implementation calls 
 401  
     * EODisplayGroup.setValueForObjectAtIndex().
 402  
     * @return True if the value was successfully set,
 403  
     * otherwise false.
 404  
     */
 405  
     public boolean setValueForAspectAtIndex ( 
 406  
         Object aValue,
 407  
         String anAspect,
 408  
         int anIndex )
 409  
     {
 410  0
         EODisplayGroup group = (EODisplayGroup)
 411  0
             aspectToGroup.objectForKey( anAspect );
 412  0
         if ( group == null ) return false;
 413  0
         String key = (String)
 414  0
             aspectToKey.objectForKey( anAspect );
 415  0
         if ( key == null ) return false;
 416  
         
 417  
         //FIXME: is this the right method to call?
 418  0
         return group.setValueForObjectAtIndex( aValue, anIndex, key );
 419  
     }
 420  
     
 421  
     /**
 422  
     * Called by subclasses to notify that the user's input
 423  
     * failed validation.  Notifies the display group for
 424  
     * the aspect, returning the result.
 425  
     * This implementation calls 
 426  
     * EODisplayGroup.associationFailedToValidateValue() 
 427  
     * with the display group's selected object.
 428  
     * @return True if the user should be allowed to continue,
 429  
     * meaning that the display group with handle user notification, 
 430  
     * otherwise false meaning the caller should notify the user.
 431  
     */
 432  
     public boolean shouldEndEditing ( 
 433  
         String anAspect,
 434  
         String anInvalidInput,
 435  
         String anErrorDescription )
 436  
     {
 437  0
         EODisplayGroup group = (EODisplayGroup)
 438  0
             aspectToGroup.objectForKey( anAspect );
 439  0
         if ( group == null ) return false;
 440  0
         String key = (String)
 441  0
             aspectToKey.objectForKey( anAspect );
 442  0
         if ( key == null ) return false;
 443  0
         return group.associationFailedToValidateValue(
 444  0
             this, anInvalidInput, key, 
 445  0
             group.selectedObject(), //FIXME: is this correct?
 446  0
             anErrorDescription );
 447  
     }
 448  
     
 449  
     /**
 450  
     * Called by subclasses to notify that the user's input
 451  
     * failed validation.  Notifies the display group for
 452  
     * the aspect, returning the result.
 453  
     * This implementation calls 
 454  
     * EODisplayGroup.associationFailedToValidateValue() 
 455  
     * with the object in the display group's displayed
 456  
     * objects array at the specified index.
 457  
     * @return True if the user should be allowed to continue,
 458  
     * meaning that the display group with handle user notification, 
 459  
     * otherwise false meaning the caller should notify the user.
 460  
     */
 461  
     public boolean shouldEndEditingAtIndex ( 
 462  
         String anAspect,
 463  
         String anInvalidInput,
 464  
         String anErrorDescription,
 465  
         int anIndex )
 466  
     {
 467  0
         EODisplayGroup group = (EODisplayGroup)
 468  0
             aspectToGroup.objectForKey( anAspect );
 469  0
         if ( group == null ) return false;
 470  0
         String key = (String)
 471  0
             aspectToKey.objectForKey( anAspect );
 472  0
         if ( key == null ) return false;
 473  0
         return group.associationFailedToValidateValue(
 474  0
             this, anInvalidInput, key, 
 475  0
             group.displayedObjects().objectAtIndex( anIndex ), 
 476  
             //FIXME: is this correct?
 477  0
             anErrorDescription );
 478  
     }
 479  
     
 480  
     /**
 481  
     * Called when either the selection or the contents of 
 482  
     * an associated display group have changed.
 483  
     * This implementation does nothing.
 484  
     */
 485  
     public void subjectChanged ()
 486  
     {
 487  
         // does nothing
 488  0
     }
 489  
     
 490  
     /**
 491  
     * Returns the current value for the display group 
 492  
     * and key associated with the specified aspect.
 493  
     */
 494  
     public Object valueForAspect ( String anAspect )
 495  
     {
 496  0
         EODisplayGroup group = (EODisplayGroup)
 497  0
             aspectToGroup.objectForKey( anAspect );
 498  0
         if ( group == null ) return null;
 499  0
         String key = (String)
 500  0
             aspectToKey.objectForKey( anAspect );
 501  0
         if ( key == null ) return null;
 502  
         
 503  
         //FIXME: is this correct?  use selected object?
 504  0
         return group.valueForObject( group.selectedObject(), key );
 505  
     }
 506  
     
 507  
     /**
 508  
     * Returns the current value for the display group 
 509  
     * and key associated with the specified aspect,
 510  
     * and the specified index (assuming multiple values).
 511  
     */
 512  
     public Object valueForAspectAtIndex ( 
 513  
         String anAspect, int anIndex )
 514  
     {
 515  0
         EODisplayGroup group = (EODisplayGroup)
 516  0
             aspectToGroup.objectForKey( anAspect );
 517  0
         if ( group == null ) return null;
 518  0
         String key = (String)
 519  0
             aspectToKey.objectForKey( anAspect );
 520  0
         if ( key == null ) return null;
 521  
         
 522  
         //FIXME: is this the right method to call?
 523  0
         return group.valueForObjectAtIndex( anIndex, key );
 524  
     }
 525  
     
 526  
 }
 527  
 
 528  
 /*
 529  
  * $Log$
 530  
  * Revision 1.2  2006/02/18 23:14:35  cgruber
 531  
  * Update imports and maven dependencies.
 532  
  *
 533  
  * Revision 1.1  2006/02/16 13:22:22  cgruber
 534  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 535  
  *
 536  
  * Revision 1.7  2005/05/11 15:21:53  cgruber
 537  
  * Change enum to enumeration, since enum is now a keyword as of Java 5.0
 538  
  *
 539  
  * A few other comments in the code.
 540  
  *
 541  
  * Revision 1.6  2003/08/06 23:07:52  chochos
 542  
  * general code cleanup (mostly, removing unused imports)
 543  
  *
 544  
  * Revision 1.5  2003/06/06 20:29:45  mpowers
 545  
  * Now dequeuing the association when connection is broken.
 546  
  * Coalesced change events had been processed even after breaking connection.
 547  
  *
 548  
  * Revision 1.4  2001/03/06 23:43:46  mpowers
 549  
  * Implemented icon aspect for text association.
 550  
  *
 551  
  * Revision 1.3  2001/02/17 16:52:05  mpowers
 552  
  * Changes in imports to support building with jdk1.1 collections.
 553  
  *
 554  
  * Revision 1.2  2001/01/25 17:46:11  mpowers
 555  
  * Clarified usage and gc expectations.
 556  
  *
 557  
  * Revision 1.1.1.1  2000/12/21 15:48:07  mpowers
 558  
  * Contributing wotonomy.
 559  
  *
 560  
  * Revision 1.11  2000/12/20 16:25:39  michael
 561  
  * Added log to all files.
 562  
  *
 563  
  *
 564  
  */
 565