Coverage Report - net.wotonomy.ui.MasterDetailAssociation
 
Classes in this File Line Coverage Branch Coverage Complexity
MasterDetailAssociation
0% 
0% 
2.067
 
 1  
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2000 Intersect Software Corporation
 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 net.wotonomy.control.EOClassDescription;
 22  
 import net.wotonomy.control.EODataSource;
 23  
 import net.wotonomy.control.EOEditingContext;
 24  
 import net.wotonomy.control.EOObserverCenter;
 25  
 import net.wotonomy.control.PropertyDataSource;
 26  
 import net.wotonomy.foundation.NSArray;
 27  
 import net.wotonomy.foundation.NSMutableArray;
 28  
 
 29  
 /**
 30  
 * MasterDetailAssociation binds a display group to a property
 31  
 * on the selected object of another display group.
 32  
 * Bindings are: 
 33  
 * <ul>
 34  
 * <li>parent: The property on the selected object of the
 35  
 * bound display group that is expected to be an indexed property.</li> 
 36  
 * </ul>
 37  
 *
 38  
 * @author michael@mpowers.net
 39  
 * @author $Author: cgruber $
 40  
 * @version $Revision: 904 $
 41  
 */
 42  0
 public class MasterDetailAssociation extends EOAssociation
 43  
 {
 44  0
     static final NSArray aspects = 
 45  0
         new NSArray( new Object[] {
 46  0
             ParentAspect
 47  
         } );
 48  0
     static final NSArray aspectSignatures = 
 49  0
         new NSArray( new Object[] {
 50  0
             AttributeToOneAspectSignature
 51  
         } );
 52  0
     static final NSArray objectKeysTaken = 
 53  0
         new NSArray( new Object[] {
 54  0
             "allObjects" 
 55  
         } );
 56  
 
 57  
     /**
 58  
     * Used to be notified of changes to objects in the
 59  
     * controlled display group.  requalify() should place 
 60  
     * all objects fetched into the controlled group into
 61  
     * this array.
 62  
     * Otherwise, the parent object is only marked as 
 63  
     * changed for inserts and deletes.
 64  
     */
 65  
     protected NSMutableArray observableArray;
 66  
                 
 67  
     /**
 68  
     * Constructor expecting an EODisplayGroup. 
 69  
     * If the controlled display group does not have a data source,
 70  
     * a new PropertyDataSource will be used.
 71  
     */
 72  
     public MasterDetailAssociation ( Object anObject )
 73  
     {
 74  0
         super( anObject );
 75  0
         observableArray = new ObservableArray( this );
 76  0
     }
 77  
     
 78  
     /**
 79  
     * Returns a List of aspect signatures whose contents
 80  
     * correspond with the aspects list.  Each element is 
 81  
     * a string whose characters represent a capability of
 82  
     * the corresponding aspect. <ul>
 83  
     * <li>"A" attribute: the aspect can be bound to
 84  
     * an attribute.</li>
 85  
     * <li>"1" to-one: the aspect can be bound to a
 86  
     * property that returns a single object.</li>
 87  
     * <li>"M" to-one: the aspect can be bound to a
 88  
     * property that returns multiple objects.</li>
 89  
     * </ul> 
 90  
     * An empty signature "" means that the aspect can
 91  
     * bind without needing a key.
 92  
     * This implementation returns "A1M" for each
 93  
     * element in the aspects array.
 94  
     */
 95  
     public static NSArray aspectSignatures ()
 96  
     {
 97  0
         return aspectSignatures;
 98  
     }
 99  
     
 100  
     /**
 101  
     * Returns a List that describes the aspects supported
 102  
     * by this class.  Each element in the list is the string
 103  
     * name of the aspect.  This implementation returns an
 104  
     * empty list.
 105  
     */
 106  
     public static NSArray aspects ()
 107  
     {
 108  0
         return aspects;
 109  
     }
 110  
     
 111  
     /**
 112  
     * Returns a List of EOAssociation subclasses that,
 113  
     * for the objects that are usable for this association,
 114  
     * are less suitable than this association.
 115  
     */
 116  
     public static NSArray associationClassesSuperseded ()
 117  
     {
 118  0
         return new NSArray();
 119  
     }
 120  
     
 121  
     /**
 122  
     * Returns whether this class can control the specified 
 123  
     * object. 
 124  
     */
 125  
     public static boolean isUsableWithObject ( Object anObject )
 126  
     {
 127  0
         return ( anObject instanceof EODisplayGroup );
 128  
     }
 129  
     
 130  
     /**
 131  
     * Returns a List of properties of the controlled object
 132  
     * that are controlled by this class.  For example,
 133  
     * "stringValue", or "selected".
 134  
     */
 135  
     public static NSArray objectKeysTaken ()
 136  
     {
 137  0
         return objectKeysTaken;
 138  
     }
 139  
     
 140  
     /**
 141  
     * Returns the aspect that is considered primary
 142  
     * or default.  This is typically "value" or somesuch.
 143  
     */
 144  
     public static String primaryAspect ()
 145  
     {
 146  0
         return ParentAspect;
 147  
     }
 148  
         
 149  
     /**
 150  
     * Returns whether this association can bind to the
 151  
     * specified display group on the specified key for
 152  
     * the specified aspect.  
 153  
     */
 154  
     public boolean canBindAspect ( 
 155  
         String anAspect, EODisplayGroup aDisplayGroup, String aKey)
 156  
     {
 157  0
         return ( aspects.containsObject( anAspect ) );
 158  
     }
 159  
     
 160  
     /**
 161  
     * Establishes a connection between this association
 162  
     * and the controlled object.  Subclasses should begin
 163  
     * listening for events from their controlled object here.
 164  
     */
 165  
     public void establishConnection ()
 166  
     {
 167  
         //NOTE: if nothing refers to this assocation, it gets gc'd.
 168  
         // otherwise, this is not needed.
 169  0
         component().addObserver( this );
 170  
 
 171  0
         EODisplayGroup displayGroup = 
 172  0
             displayGroupForAspect( ParentAspect );
 173  0
         String key = 
 174  0
             displayGroupKeyForAspect( ParentAspect );
 175  
 
 176  
         // obtain and qualify new data source from existing source if necessary
 177  0
         if ( component().dataSource() == null ) 
 178  
         {
 179  0
             if ( ( displayGroup != null ) && ( displayGroup.dataSource() != null ) )
 180  
             {
 181  0
                 component().setDataSource( 
 182  0
                     displayGroup.dataSource().
 183  0
                         dataSourceQualifiedByKey( key ) );
 184  
             }
 185  
         }
 186  
 
 187  
         // set up proxy data source if necessary
 188  0
         if ( component().dataSource() == null )
 189  
         {
 190  
             // get context and class desc from master group
 191  0
             EOEditingContext editingContext = null;
 192  0
             EOClassDescription classDesc = null;
 193  0
             if ( displayGroup != null )
 194  
             {
 195  0
                 EODataSource dataSource = displayGroup.dataSource();
 196  0
                 if ( dataSource != null )
 197  
                 {
 198  0
                     editingContext = dataSource.editingContext();
 199  0
                     EOClassDescription parentDesc = dataSource.classDescriptionForObjects();
 200  0
                     if ( parentDesc != null )
 201  
                     {
 202  0
                         classDesc = parentDesc.classDescriptionForDestinationKey( key );   
 203  
                     }
 204  
                 }
 205  
             }
 206  
             
 207  
             //FIXME: should this be called DetailDataSource?
 208  0
             component().setDataSource( 
 209  0
                new PropertyDataSource( editingContext, classDesc ) );
 210  
         }
 211  
 
 212  0
         super.establishConnection();
 213  0
                 requalify();
 214  0
     }
 215  
     
 216  
     /**
 217  
     * Breaks the connection between this association and 
 218  
     * its object.  Override to stop listening for events
 219  
     * from the object.
 220  
     */
 221  
     public void breakConnection ()
 222  
     {
 223  
         //NOTE: if nothing refers to this assocation, it gets gc'd.
 224  
         // otherwise, this is not needed.
 225  0
         component().deleteObserver( this );        
 226  
 
 227  0
         super.breakConnection();
 228  0
     }
 229  
         
 230  
     /**
 231  
     * Called when either the selection or the contents 
 232  
     * of an associated display group have changed.
 233  
     */
 234  
     public void subjectChanged ()
 235  
     {
 236  
                 EODisplayGroup displayGroup;
 237  
 
 238  
                 // parent aspect
 239  0
                 displayGroup = displayGroupForAspect( ParentAspect );
 240  0
                 if ( displayGroup != null )
 241  
                 {
 242  0
                         if ( displayGroup.selectionChanged() )
 243  
                         {
 244  0
                 requalify();
 245  0
                         }
 246  
                         else
 247  0
                         if ( displayGroup.contentsChanged() )
 248  
                         {
 249  0
                 requalify();
 250  
                         }
 251  
                 }
 252  0
     }
 253  
 
 254  
     /**
 255  
     * Overridden to intercept notifications of changes to objects 
 256  
     * in the controlled display group and broadcast a change on the 
 257  
     * parent group's selected object.  All other notifications are
 258  
     * passed to the super implementation.
 259  
     */
 260  
     public void objectWillChange ( Object anObject )
 261  
     {
 262  
         // if child display group is notifying
 263  0
         if ( ! ( anObject instanceof EODisplayGroup ) )
 264  
         {
 265  
             // mark parent group's object as changed
 266  0
             EODisplayGroup displayGroup = displayGroupForAspect( ParentAspect );
 267  0
             if ( displayGroup != null )
 268  
             {
 269  0
                 Object selected = displayGroup.selectedObject();
 270  0
                 if ( selected != null )
 271  
                 {
 272  
                     // only notify if childrenKey is an attribute of parentDesc
 273  
                     // (and therefore not a toOne or toMany relationship)
 274  0
                     EOClassDescription parentDesc = 
 275  0
                         EOClassDescription.classDescriptionForClass( 
 276  0
                             selected.getClass() );
 277  0
                     String key = displayGroupKeyForAspect( ParentAspect );
 278  0
                     if ( key != null )
 279  
                     {
 280  0
                         int idx = key.indexOf( '.' );
 281  0
                         if ( idx != -1 ) key = key.substring( 0, idx );
 282  0
                         if ( parentDesc.attributeKeys().contains( key ) )
 283  
                         {
 284  
                             // only notify if we are an attribute key
 285  0
                             EOObserverCenter.notifyObserversObjectWillChange( selected );
 286  
                         }
 287  
                     }
 288  
                 }
 289  
             }
 290  0
         }
 291  
         else // display group is notifying
 292  
         {
 293  
             // call super so subjectChanged will be called
 294  0
             super.objectWillChange( anObject );
 295  
         }
 296  0
     }
 297  
     
 298  
     /**
 299  
     * Called by subjectChanged() to requalify the controlled
 300  
     * display group with the selected object and the bound key.
 301  
     */
 302  
         protected void requalify()
 303  
         {
 304  0
                 EODisplayGroup component = component();
 305  0
                 EODisplayGroup displayGroup = displayGroupForAspect( ParentAspect );
 306  0
                 String key = displayGroupKeyForAspect( ParentAspect );
 307  
                 
 308  0
                 if ( ( displayGroup.selectedObject() != null )
 309  0
         && ( component.dataSource() != null ) )
 310  
                 {
 311  0
                         component.dataSource().qualifyWithRelationshipKey( 
 312  0
                                 key, displayGroup.selectedObject() );  
 313  0
                         component.fetch();
 314  0
             observableArray.setArray( component.allObjects() );
 315  0
                 }
 316  
                 else // no selection or no data source, clear
 317  
                 {
 318  0
                         component.setObjectArray( null );
 319  0
             observableArray.removeAllObjects();
 320  
                 }
 321  0
         component.updateDisplayedObjects();
 322  0
         }
 323  
                   
 324  
     /**
 325  
         * This implementation returns ObserverPrioritySecond
 326  
         * so that master detail assocations are notified before
 327  
     * other associations.
 328  
     */
 329  
     public int priority ()
 330  
     {
 331  0
         return ObserverPrioritySecond;
 332  
     }
 333  
 
 334  
         // convenience
 335  
 
 336  
     private EODisplayGroup component()
 337  
     {
 338  0
         return (EODisplayGroup) object();
 339  
     }
 340  
     
 341  
 }
 342  
 
 343  
 /*
 344  
  * $Log$
 345  
  * Revision 1.2  2006/02/18 23:14:35  cgruber
 346  
  * Update imports and maven dependencies.
 347  
  *
 348  
  * Revision 1.1  2006/02/16 13:22:22  cgruber
 349  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 350  
  *
 351  
  * Revision 1.14  2004/02/04 20:00:49  mpowers
 352  
  * Improved change notification for dotted key paths.
 353  
  *
 354  
  * Revision 1.13  2003/08/06 23:07:52  chochos
 355  
  * general code cleanup (mostly, removing unused imports)
 356  
  *
 357  
  * Revision 1.12  2001/10/26 18:39:05  mpowers
 358  
  * Now a delayed observer with a higher priority, so that it is processed
 359  
  * before other associations.
 360  
  *
 361  
  * Revision 1.11  2001/06/26 21:39:33  mpowers
 362  
  * Added check for null component data source before requalifying.
 363  
  *
 364  
  * Revision 1.10  2001/05/21 14:04:15  mpowers
 365  
  * No longer changing a detail group's data source if it's already specified.
 366  
  *
 367  
  * Revision 1.9  2001/05/18 21:08:46  mpowers
 368  
  * Now calling updateDisplayedObjects on detail after master changes.
 369  
  *
 370  
  * Revision 1.8  2001/05/14 15:26:42  mpowers
 371  
  * Modified logic for controlled groups that have no data source already set.
 372  
  *
 373  
  * Revision 1.7  2001/05/04 14:42:58  mpowers
 374  
  * Now getting stored values in KeyValueCoding.
 375  
  * MasterDetail now marks dirty based on whether it's an attribute
 376  
  * or relation.
 377  
  * Implemented editing context marker.
 378  
  *
 379  
  * Revision 1.6  2001/04/29 02:29:31  mpowers
 380  
  * Debugging relationship faulting.
 381  
  *
 382  
  * Revision 1.4  2001/02/20 16:38:55  mpowers
 383  
  * MasterDetailAssociations now observe their controlled display group's
 384  
  * objects for changes to that the parent object will be marked as updated.
 385  
  * Before, only inserts and deletes to an object's items are registered.
 386  
  * Also, moved ObservableArray to package access.
 387  
  *
 388  
  * Revision 1.3  2001/01/18 16:57:18  mpowers
 389  
  * Fixed problem with losing connection: the association was getting
 390  
  * garbage collected because nothing referred to it.  All other associations
 391  
  * make themselves listeners of their controlled object, and that has been
 392  
  * the only thing keeping them from getting gc'd.  This will need to be fixed.
 393  
  *
 394  
  * Revision 1.2  2001/01/17 23:06:09  mpowers
 395  
  * TreeAssociation now modifies the contents of the children display
 396  
  * group rather than adding items to the titles display group.
 397  
  *
 398  
  * Revision 1.1.1.1  2000/12/21 15:48:23  mpowers
 399  
  * Contributing wotonomy.
 400  
  *
 401  
  * Revision 1.5  2000/12/20 16:25:40  michael
 402  
  * Added log to all files.
 403  
  *
 404  
  *
 405  
  */
 406