Coverage Report - net.wotonomy.ui.swing.DateAssociation
 
Classes in this File Line Coverage Branch Coverage Complexity
DateAssociation
0% 
0% 
3.812
 
 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.swing;
 20  
 
 21  
 import java.awt.Component;
 22  
 import java.awt.event.ActionEvent;
 23  
 import java.awt.event.ActionListener;
 24  
 import java.awt.event.FocusEvent;
 25  
 import java.awt.event.FocusListener;
 26  
 import java.util.Calendar;
 27  
 import java.util.Date;
 28  
 import java.util.Enumeration;
 29  
 import java.util.Iterator;
 30  
 
 31  
 import net.wotonomy.foundation.NSArray;
 32  
 import net.wotonomy.foundation.NSSelector;
 33  
 import net.wotonomy.foundation.internal.ValueConverter;
 34  
 import net.wotonomy.foundation.internal.WotonomyException;
 35  
 import net.wotonomy.ui.EOAssociation;
 36  
 import net.wotonomy.ui.EODisplayGroup;
 37  
 
 38  
 /**
 39  
 * DateAssociation binds any component that has a set and get Date methods and
 40  
 * fire actions events when the date has been changed.  Bindings are:
 41  
 * <ul>
 42  
 * <li>value: a property convertable to/from a date</li>
 43  
 * <li>editable: a boolean property that determines whether
 44  
 * the user can edit the date in the component</li>
 45  
 * <li>enabled: a boolean property that determines whether
 46  
 * the component is enabled or disabled</li>
 47  
 * </ul>
 48  
 *
 49  
 * @author rob@yahoo.com
 50  
 * @version $Revision: 904 $
 51  
 */
 52  0
 public class DateAssociation extends EOAssociation
 53  
                              implements ActionListener, FocusListener
 54  
 {
 55  0
     static final NSArray aspects =
 56  0
         new NSArray( new Object[] {
 57  0
             ValueAspect, EnabledAspect, EditableAspect
 58  
         } );
 59  0
     static final NSArray aspectSignatures =
 60  0
         new NSArray( new Object[] {
 61  0
             AttributeToOneAspectSignature,
 62  0
                         AttributeToOneAspectSignature,
 63  0
                         AttributeToOneAspectSignature
 64  
         } );
 65  0
     static final NSArray objectKeysTaken =
 66  0
         new NSArray( new Object[] {
 67  0
             "date", "enabled", "editable"
 68  
         } );
 69  
 
 70  0
         private final static NSSelector getDate =
 71  0
                 new NSSelector( "getDate" );
 72  0
         private final static NSSelector setDate =
 73  0
                 new NSSelector( "setDate",
 74  0
                 new Class[] { Date.class } );
 75  0
         private final static NSSelector addActionListener =
 76  0
                 new NSSelector( "addActionListener",
 77  0
                 new Class[] { ActionListener.class } );
 78  0
         private final static NSSelector removeActionListener =
 79  0
                 new NSSelector( "removeActionListener",
 80  0
                 new Class[] { ActionListener.class } );
 81  0
         private final static NSSelector addFocusListener =
 82  0
                 new NSSelector( "addFocusListener",
 83  0
                 new Class[] { FocusListener.class } );
 84  0
         private final static NSSelector removeFocusListener =
 85  0
                 new NSSelector( "removeFocusListener",
 86  0
                 new Class[] { FocusListener.class } );
 87  0
         private final static NSSelector setEditable =
 88  0
                 new NSSelector( "setEditable",
 89  0
                 new Class[] { boolean.class } );
 90  
 
 91  
         // dirty handling
 92  
         private boolean needsUpdate;
 93  
     private Date nullValue; // placeholder for null value flag
 94  
 
 95  
     /**
 96  
     * Constructor specifying the object to be controlled by this
 97  
     * association.  Does not establish connection.
 98  
     */
 99  
     public DateAssociation ( Object anObject )
 100  
     {
 101  0
         super( anObject );
 102  0
                 needsUpdate = false;
 103  0
         nullValue = null;
 104  0
     }
 105  
 
 106  
     /**
 107  
     * Returns a List of aspect signatures whose contents
 108  
     * correspond with the aspects list.  Each element is
 109  
     * a string whose characters represent a capability of
 110  
     * the corresponding aspect. <ul>
 111  
     * <li>"A" attribute: the aspect can be bound to
 112  
     * an attribute.</li>
 113  
     * <li>"1" to-one: the aspect can be bound to a
 114  
     * property that returns a single object.</li>
 115  
     * <li>"M" to-one: the aspect can be bound to a
 116  
     * property that returns multiple objects.</li>
 117  
     * </ul>
 118  
     * An empty signature "" means that the aspect can
 119  
     * bind without needing a key.
 120  
     * This implementation returns "A1M" for each
 121  
     * element in the aspects array.
 122  
     */
 123  
     public static NSArray aspectSignatures ()
 124  
     {
 125  0
         return aspectSignatures;
 126  
     }
 127  
 
 128  
     /**
 129  
     * Returns a List that describes the aspects supported
 130  
     * by this class.  Each element in the list is the string
 131  
     * name of the aspect.  This implementation returns an
 132  
     * empty list.
 133  
     */
 134  
     public static NSArray aspects ()
 135  
     {
 136  0
         return aspects;
 137  
     }
 138  
 
 139  
     /**
 140  
     * Returns a List of EOAssociation subclasses that,
 141  
     * for the objects that are usable for this association,
 142  
     * are less suitable than this association.
 143  
     */
 144  
     public static NSArray associationClassesSuperseded ()
 145  
     {
 146  0
         return new NSArray();
 147  
     }
 148  
 
 149  
     /**
 150  
     * Returns whether this class can control the specified
 151  
     * object.
 152  
     */
 153  
     public static boolean isUsableWithObject ( Object anObject )
 154  
     {
 155  0
         return setDate.implementedByObject( anObject );
 156  
     }
 157  
 
 158  
     /**
 159  
     * Returns a List of properties of the controlled object
 160  
     * that are controlled by this class.  For example,
 161  
     * "stringValue", or "selected".
 162  
     */
 163  
     public static NSArray objectKeysTaken ()
 164  
     {
 165  0
         return objectKeysTaken;
 166  
     }
 167  
 
 168  
     /**
 169  
     * Returns the aspect that is considered primary
 170  
     * or default.  This is typically "value" or somesuch.
 171  
     */
 172  
     public static String primaryAspect ()
 173  
     {
 174  0
         return ValueAspect;
 175  
     }
 176  
 
 177  
     /**
 178  
     * Returns whether this association can bind to the
 179  
     * specified display group on the specified key for
 180  
     * the specified aspect.
 181  
     */
 182  
     public boolean canBindAspect (
 183  
         String anAspect, EODisplayGroup aDisplayGroup, String aKey)
 184  
     {
 185  0
         return ( aspects.containsObject( anAspect ) );
 186  
     }
 187  
 
 188  
     /**
 189  
     * Establishes a connection between this association
 190  
     * and the controlled object.  This implementation
 191  
         * attempts to add this class as an ActionListener
 192  
         * and Focus Listener to the specified object.
 193  
     */
 194  
     public void establishConnection ()
 195  
     {
 196  0
                 Object component = object();
 197  
                 try
 198  
                 {
 199  0
                         if ( addActionListener.implementedByObject( component ) )
 200  
                         {
 201  0
                                 addActionListener.invoke( component, this );
 202  
                         }
 203  0
                         if ( addFocusListener.implementedByObject( component ) )
 204  
                         {
 205  0
                                 addFocusListener.invoke( component, this );
 206  
                         }
 207  
                 }
 208  0
                 catch ( Exception exc )
 209  
                 {
 210  0
                         throw new WotonomyException(
 211  0
                                 "Error while establishing connection", exc );
 212  0
                 }
 213  
 
 214  0
         super.establishConnection();
 215  
 
 216  
                 // forces update from bindings
 217  0
                 subjectChanged();
 218  0
     }
 219  
 
 220  
     /**
 221  
     * Breaks the connection between this association and
 222  
     * its object.  Override to stop listening for events
 223  
     * from the object.
 224  
     */
 225  
     public void breakConnection ()
 226  
     {
 227  0
                 Object component = object();
 228  
                 try
 229  
                 {
 230  0
                         if ( removeActionListener.implementedByObject( component ) )
 231  
                         {
 232  0
                                 removeActionListener.invoke( component, this );
 233  
                         }
 234  0
                         if ( removeFocusListener.implementedByObject( component ) )
 235  
                         {
 236  0
                                 removeFocusListener.invoke( component, this );
 237  
                         }
 238  
                 }
 239  0
                 catch ( Exception exc )
 240  
                 {
 241  0
                         throw new WotonomyException(
 242  0
                                 "Error while breaking connection", exc );
 243  0
                 }
 244  0
         super.breakConnection();
 245  0
     }
 246  
 
 247  
     /**
 248  
     * Called when either the selection or the contents
 249  
     * of an associated display group have changed.
 250  
     */
 251  
     public void subjectChanged ()
 252  
     {
 253  0
                 Object component = object();
 254  
                 EODisplayGroup displayGroup;
 255  
                 String key;
 256  
                 Object value;
 257  
 
 258  
                 // value aspect
 259  0
                 displayGroup = displayGroupForAspect( ValueAspect );
 260  0
                 if ( displayGroup != null )
 261  
                 {
 262  0
                     key = displayGroupKeyForAspect( ValueAspect );
 263  0
                     if ( component instanceof Component )
 264  
                     {
 265  0
                         ((Component)component).setEnabled( 
 266  0
                                 displayGroup.enabledToSetSelectedObjectValueForKey( key ) );
 267  
                     }
 268  0
                     if ( displayGroup.selectedObjects().size() > 1 )
 269  
                     {
 270  
                         // if there're more than one object selected, set
 271  
                         // the value to blank for all of them.
 272  
                         Object previousValue;
 273  
 
 274  0
                         Iterator indexIterator = displayGroup.selectionIndexes().
 275  0
                                                   iterator();
 276  
 
 277  
                         // get value for the first selected object.
 278  0
                         int initialIndex = ( (Integer)indexIterator.next() ).intValue();
 279  0
                         previousValue = displayGroup.valueForObjectAtIndex(
 280  0
                                                   initialIndex, key );
 281  0
                         value = null;
 282  
 
 283  
                         // go through the rest of the selected objects, compare each
 284  
                         // value with the previous one. continue comparing if two
 285  
                         // values are equal, break the while loop if they're different.
 286  
                         // the final value will be the common value of all selected objects
 287  
                         // if there is one, or be blank if there is not.
 288  0
                         while ( indexIterator.hasNext() )
 289  
                         {
 290  0
                             int index = ( (Integer)indexIterator.next() ).intValue();
 291  0
                             Object currentValue = displayGroup.valueForObjectAtIndex(
 292  0
                                                   index, key );
 293  0
                             if ( currentValue != null && !currentValue.equals( previousValue ) )
 294  
                             {
 295  0
                                 value = null;
 296  0
                                 break;
 297  
                             }
 298  
                             else
 299  
                             {
 300  
                                 // currentValue is the same as the previous one
 301  0
                                 value = currentValue;
 302  
                             }
 303  
 
 304  0
                         } // end while
 305  
 
 306  0
                     } else {
 307  
 
 308  0
                         value = displayGroup.selectedObjectValueForKey( key );
 309  
                     } // end checking size of displayGroup
 310  
 
 311  
                     // convert value to date
 312  
                     try
 313  
                     {
 314  0
                         Date dateValue = null;
 315  
                         // (Date) ValueConverter.convertObjectToClass( value, Date.class );
 316  
 
 317  0
                         if ( value instanceof Date )
 318  
                         {
 319  0
                             dateValue = (Date) value;
 320  
                         }
 321  
 
 322  0
                         if ( ( dateValue == null ) && ( value instanceof Calendar ) )
 323  
                         {
 324  0
                             dateValue = ( ( Calendar )value ).getTime();
 325  
                         }
 326  
 
 327  0
                         if ( dateValue == null )
 328  
                         {
 329  
                             // current time (placeholder)
 330  0
                             nullValue = new Date();
 331  0
                             dateValue = nullValue;
 332  0
                         }
 333  
                         else
 334  
                         {
 335  0
                             nullValue = null;
 336  
                         }
 337  
 
 338  0
                         if ( !dateValue.equals( getDate.invoke( component ) ) )
 339  
                         {        // No need to update if there is no change.
 340  0
                             setDate.invoke( component, dateValue );
 341  0
                             needsUpdate = false;
 342  
                         }
 343  
 
 344  
                     }
 345  0
                     catch ( Exception exc )
 346  
                     {
 347  0
                             throw new WotonomyException(
 348  0
                                     "Error while updating component connection", exc );
 349  0
                     }
 350  
                 }
 351  
 
 352  
                 // enabled aspect
 353  0
                 displayGroup = displayGroupForAspect( EnabledAspect );
 354  0
                 key = displayGroupKeyForAspect( EnabledAspect );
 355  0
                 if ( ( ( displayGroup != null ) || ( key != null ) )
 356  0
                 && ( component instanceof Component ) )
 357  
                 {
 358  0
                         if ( displayGroup != null )
 359  
                         {
 360  0
                                 value =
 361  0
                                         displayGroup.selectedObjectValueForKey( key );
 362  0
                         }
 363  
                         else
 364  
                         {
 365  
                                 // treat bound key without display group as a value
 366  0
                                 value = key;
 367  
                         }
 368  0
             Boolean converted = null;
 369  0
             if ( value != null ) 
 370  
             {
 371  0
                 converted = (Boolean)
 372  0
                     ValueConverter.convertObjectToClass(
 373  0
                         value, Boolean.class );
 374  
             }
 375  0
             if ( converted == null ) converted = Boolean.FALSE;
 376  0
             if ( converted.booleanValue() != ((Component)component).isEnabled() )
 377  
             {
 378  0
                 ((Component)component).setEnabled( converted.booleanValue() );
 379  
             }
 380  
                 }
 381  
 
 382  
                 // editable aspect
 383  0
                 displayGroup = displayGroupForAspect( EditableAspect );
 384  0
                 key = displayGroupKeyForAspect( EditableAspect );
 385  0
                 if ( ( ( displayGroup != null ) || ( key != null ) )
 386  0
                 && ( setEditable.implementedByObject( component ) ) )
 387  
                 {
 388  
             try
 389  
             {
 390  0
                 if ( displayGroup != null )
 391  
                 {
 392  0
                     value =
 393  0
                         displayGroup.selectedObjectValueForKey( key );
 394  0
                 }
 395  
                 else
 396  
                 {
 397  
                     // treat bound key without display group as a value
 398  0
                     value = key;
 399  
                 }
 400  0
                 Boolean converted = (Boolean)
 401  0
                     ValueConverter.convertObjectToClass(
 402  0
                         value, Boolean.class );
 403  
 
 404  0
                 if ( converted != null )
 405  
                 {
 406  0
                     setEditable.invoke( component, converted );
 407  
                 }
 408  
             }
 409  0
                         catch ( Exception exc )
 410  
                         {
 411  0
                                 throw new WotonomyException(
 412  0
                                         "Error while updating component connection (editable aspect)", exc );
 413  0
                         }
 414  
                 }
 415  0
     }
 416  
 
 417  
     /**
 418  
     * Forces this association to cause the object to
 419  
     * stop editing and validate the user's input.
 420  
     * @return false if there were problems validating,
 421  
     * or true to continue.
 422  
     */
 423  
     public boolean endEditing ()
 424  
     {
 425  0
                 return writeValueToDisplayGroup();
 426  
     }
 427  
 
 428  
         /**
 429  
         * Writes the value currently in the component
 430  
         * to the selected object in the display group
 431  
         * bound to the value aspect.
 432  
         * @return false if there were problems validating,
 433  
         * or true to continue.
 434  
         */
 435  
         protected boolean writeValueToDisplayGroup()
 436  
         {
 437  0
             if ( !needsUpdate ) return true;
 438  
 
 439  0
             EODisplayGroup displayGroup =
 440  0
                     displayGroupForAspect( ValueAspect );
 441  0
             if ( displayGroup != null )
 442  
             {
 443  0
                 String key = displayGroupKeyForAspect( ValueAspect );
 444  0
                                 Object component = object();
 445  0
                 Object value = null;
 446  
                 try
 447  
                 {
 448  0
                     if ( getDate.implementedByObject( component ) )
 449  
                     {
 450  0
                             value = getDate.invoke( component );
 451  
                     }
 452  0
                     if ( nullValue != null )
 453  
                     {
 454  0
                         if ( nullValue.equals( value ) )
 455  
                         {
 456  0
                             value = null;
 457  
                         }
 458  
                     }
 459  
                 }
 460  0
                 catch ( Exception exc )
 461  
                 {
 462  0
                         throw new WotonomyException(
 463  0
                                 "Error updating display group", exc );
 464  0
                 }
 465  
 
 466  0
                 needsUpdate = false;
 467  
 
 468  0
                 boolean returnValue = true;
 469  0
                 Iterator selectedIterator = displayGroup.selectionIndexes().iterator();
 470  0
                 while ( selectedIterator.hasNext() )
 471  
                 {
 472  0
                     int index = ( (Integer)selectedIterator.next() ).intValue();
 473  
 
 474  0
                     if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) )
 475  
                     {
 476  0
                         returnValue = false;
 477  
                     }
 478  0
                 }
 479  0
                 return returnValue;
 480  
 
 481  
             }
 482  0
             return false;
 483  
         }
 484  
 
 485  
     // interface ActionListener
 486  
 
 487  
         /**
 488  
         * Updates object on action performed.
 489  
         */
 490  
         public void actionPerformed( ActionEvent evt )
 491  
         {
 492  0
         needsUpdate = true;
 493  0
                 writeValueToDisplayGroup();  // TODO: Should we do this here or on focus lost?
 494  0
         }
 495  
 
 496  
     // interface FocusListener
 497  
 
 498  
         /**
 499  
         * Notifies of beginning of edit.
 500  
         */
 501  
     public void focusGained(FocusEvent evt)
 502  
     {
 503  
                 Object o;
 504  
                 EODisplayGroup displayGroup;
 505  0
         Enumeration e = aspects().objectEnumerator();
 506  0
                 while ( e.hasMoreElements() )
 507  
                 {
 508  0
                         displayGroup =
 509  0
                                 displayGroupForAspect( e.nextElement().toString() );
 510  0
                         if ( displayGroup != null )
 511  
                         {
 512  0
                                 displayGroup.associationDidBeginEditing( this );
 513  0
                         }
 514  
                 }
 515  0
     }
 516  
 
 517  
         /**
 518  
         * Updates object on focus lost and notifies of end of edit.
 519  
         */
 520  
     public void focusLost(FocusEvent evt)
 521  
     {
 522  0
                 if ( endEditing() )
 523  
                 {
 524  
                         Object o;
 525  
                         EODisplayGroup displayGroup;
 526  0
                         Enumeration e = aspects().objectEnumerator();
 527  0
                         while ( e.hasMoreElements() )
 528  
                         {
 529  0
                                 displayGroup =
 530  0
                                         displayGroupForAspect( e.nextElement().toString() );
 531  0
                                 if ( displayGroup != null )
 532  
                                 {
 533  0
                                         displayGroup.associationDidEndEditing( this );
 534  0
                                 }
 535  
                         }
 536  
                 }
 537  
                 else
 538  
                 {
 539  
                         // probably should notify of a validation error here,
 540  
                         // but how to also handle actionPerformed without copying code?
 541  
 /*
 542  
                         Object value = null;
 543  
                         try
 544  
                         {
 545  
                                 if ( getText.implementedByObject( object() ) )
 546  
                                 {
 547  
                                         value = getText.invoke( object() );
 548  
                                 }
 549  
                         }
 550  
                         catch ( Exception exc )
 551  
                         {
 552  
                                 throw new WotonomyException(
 553  
                                         "Error updating display group", exc );
 554  
                         }
 555  
 
 556  
                         EODisplayGroup displayGroup =
 557  
                                         displayGroupForAspect( ValueAspect );
 558  
                         String key = displayGroupKeyForAspect( ValueAspect );
 559  
                         if ( displayGroup != null )
 560  
                         {
 561  
                                 if ( displayGroup.associationFailedToValidateValue(
 562  
                                         this, (String) value, key, object(),
 563  
                                         "That format was not recognized." ) )
 564  
                                 {
 565  
                                         new net.wotonomy.ui.swing.util.StackTraceInspector();
 566  
                                 }
 567  
                                 if ( object() instanceof Component )
 568  
                                 {
 569  
                                         ((Component)object()).requestFocus();
 570  
                                 }
 571  
                         }
 572  
 */
 573  
                 }
 574  0
     }
 575  
 }
 576  
 
 577  
 /*
 578  
  * $Log$
 579  
  * Revision 1.2  2006/02/18 23:19:05  cgruber
 580  
  * Update imports and maven dependencies.
 581  
  *
 582  
  * Revision 1.1  2006/02/16 13:22:22  cgruber
 583  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 584  
  *
 585  
  * Revision 1.7  2004/01/28 18:34:57  mpowers
 586  
  * Better handling for enabling.
 587  
  * Now respecting enabledToSetSelectedObjectValueForKey from display group.
 588  
  *
 589  
  * Revision 1.6  2003/08/06 23:07:52  chochos
 590  
  * general code cleanup (mostly, removing unused imports)
 591  
  *
 592  
  * Revision 1.5  2001/07/30 16:32:55  mpowers
 593  
  * Implemented support for bulk-editing.  Detail associations will now
 594  
  * apply changes to all selected objects.
 595  
  *
 596  
  * Revision 1.4  2001/02/17 17:23:49  mpowers
 597  
  * More changes to support compiling with jdk1.1 collections.
 598  
  *
 599  
  * Revision 1.3  2001/02/17 16:52:05  mpowers
 600  
  * Changes in imports to support building with jdk1.1 collections.
 601  
  *
 602  
  * Revision 1.2  2001/01/17 16:25:26  mpowers
 603  
  * Now catching null values from data object.
 604  
  *
 605  
  * Revision 1.1  2001/01/10 22:26:32  mpowers
 606  
  * Contributing DateAssociation.
 607  
  *
 608  
  * Revision 1.1  2001/01/10 21:30:27  rglista
 609  
  * Initial checkin
 610  
  *
 611  
  *
 612  
  */
 613