View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2001 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 java.util.HashSet;
22  import java.util.Iterator;
23  import java.util.Set;
24  
25  import net.wotonomy.control.EOKeyValueCoding;
26  import net.wotonomy.control.EOKeyValueCodingSupport;
27  import net.wotonomy.control.EOObserverCenter;
28  import net.wotonomy.foundation.NSArray;
29  
30  /***
31  * GenericAssociation binds one or more properties on an 
32  * observable object to a display group.  The controlled
33  * object is expected to use the ObserverCenter and will
34  * be observed by this association. <br><br>
35  *
36  * Bindings for this association are <i>generic</i>: the 
37  * name of the aspect will be treated as a property key 
38  * on the displayed object(s) and synchronized with the 
39  * bound property on the controlled object. <br><br>
40  *
41  * NOTE: because we cannot assume that the controlled
42  * object will retain a reference to this association,
43  * you must explicitly retain a reference to prevent
44  * the association from getting garbage collected.
45  *
46  * @author michael@mpowers.net
47  * @author $Author: cgruber $
48  * @version $Revision: 904 $
49  */
50  public class GenericAssociation extends EOAssociation
51  {
52      protected boolean objectModified;
53      protected Set aspectsModified;
54      
55      static final NSArray aspects = 
56          new NSArray( new Object[] {
57          } );
58      static final NSArray aspectSignatures = 
59          new NSArray( new Object[] {
60          } );
61      static final NSArray objectKeysTaken = 
62          new NSArray( new Object[] {
63          } );
64  		
65      /***
66      * Constructor specifying the object to be controlled by this
67      * association.  Does not establish connection.
68      */
69      public GenericAssociation ( Object anObject )
70      {
71          super( anObject );
72          objectModified = false;
73          aspectsModified = new HashSet();
74      }
75      
76      /***
77      * Returns a List of aspect signatures whose contents
78      * correspond with the aspects list.  Each element is 
79      * a string whose characters represent a capability of
80      * the corresponding aspect. <ul>
81      * <li>"A" attribute: the aspect can be bound to
82      * an attribute.</li>
83      * <li>"1" to-one: the aspect can be bound to a
84      * property that returns a single object.</li>
85      * <li>"M" to-one: the aspect can be bound to a
86      * property that returns multiple objects.</li>
87      * </ul> 
88      * An empty signature "" means that the aspect can
89      * bind without needing a key.
90      * This implementation returns "A1M" for each
91      * element in the aspects array.
92      */
93      public static NSArray aspectSignatures ()
94      {
95          return aspectSignatures;
96      }
97      
98      /***
99      * Returns a List that describes the aspects supported
100     * by this class.  Each element in the list is the string
101     * name of the aspect.  This implementation returns an
102     * empty list.
103     */
104     public static NSArray aspects ()
105     {
106         return aspects;
107     }
108     
109     /***
110     * Returns a List of EOAssociation subclasses that,
111     * for the objects that are usable for this association,
112     * are less suitable than this association.
113     */
114     public static NSArray associationClassesSuperseded ()
115     {
116         return new NSArray();
117     }
118     
119     /***
120     * Returns whether this class can control the specified 
121     * object. 
122     */
123     public static boolean isUsableWithObject ( Object anObject )
124     {
125         return true;
126     }
127     
128     /***
129     * Returns a List of properties of the controlled object
130     * that are controlled by this class.  For example,
131     * "stringValue", or "selected".
132     */
133     public static NSArray objectKeysTaken ()
134     {
135         return objectKeysTaken;
136     }
137     
138     /***
139     * Returns the aspect that is considered primary
140     * or default.  This is typically "value" or somesuch.
141     */
142     public static String primaryAspect ()
143     {
144         return "";
145     }
146         
147     /***
148     * Returns whether this association can bind to the
149     * specified display group on the specified key for
150     * the specified aspect.  
151     */
152     public boolean canBindAspect ( 
153         String anAspect, EODisplayGroup aDisplayGroup, String aKey)
154     {
155         return true;
156     }
157     
158     /***
159     * Establishes a connection between this association
160     * and the controlled object.  This implementation 
161 	* registers with ObserverCenter for change notifications
162     * from the controlled object.
163     */
164     public void establishConnection ()
165     {
166 		EOObserverCenter.addObserver( this, object() );
167         super.establishConnection();
168 		
169 		// forces update from bindings
170 		subjectChanged();
171     }
172 	
173     /***
174     * Breaks the connection between this association and 
175     * its object.  Override to stop listening for events
176     * from the object.
177     */
178     public void breakConnection ()
179     {
180 		EOObserverCenter.removeObserver( this, object() );
181         super.breakConnection();
182     }
183     
184     /***
185     * Overridden to track which observed object is changing.
186     */
187     public void objectWillChange( Object anObject )
188     {
189         if ( object() == anObject ) 
190         { 
191             objectModified = true;
192         }
193         else
194         {
195             aspectsModified.add( aspectToGroup.allKeysForObject( anObject ) );
196         }
197     }
198 
199     /***
200     * Called when either the selection or the contents 
201     * of an associated display group have changed.
202     */
203     public void subjectChanged ()
204     {
205         String aspect;
206         String key;
207         Object value;
208         EODisplayGroup displayGroup;
209         Iterator iterator;
210         
211         iterator = aspectsModified.iterator();
212         while ( iterator.hasNext() )
213         {
214             aspect = (String) iterator.next();
215             key = displayGroupKeyForAspect( aspect );
216             displayGroup = displayGroupForAspect( aspect );
217 
218             value = readValueFromDisplayGroupForKey( displayGroup, key );    
219             writeValueForKey( object(), value, key );
220         }
221         aspectsModified.clear();
222         
223         if ( objectModified )
224         {
225             iterator = aspectToGroup.keySet().iterator();
226             while ( iterator.hasNext() )
227             {
228                 aspect = (String) iterator.next();
229                 key = displayGroupKeyForAspect( aspect );
230                 value = readValueForKey( object(), key );
231                 displayGroup = displayGroupForAspect( aspect );
232                 displayGroup.setSelectedObjectValue( value, key );
233             }
234         }
235     }
236     
237     protected Object readValueForKey( Object object, String key )
238     {
239         if ( object instanceof EOKeyValueCoding )
240         {
241             return ((EOKeyValueCoding)object).valueForKey( key );
242         }
243         return EOKeyValueCodingSupport.valueForKey( object, key );
244     }
245     
246     protected void writeValueForKey( Object object, Object value, String key )
247     {
248         if ( object instanceof EOKeyValueCoding )
249         {
250             ((EOKeyValueCoding)object).takeValueForKey( value, key );
251         }
252         else
253         {
254             EOKeyValueCodingSupport.takeValueForKey( object, value, key );
255         }
256     }
257     
258     protected Object readValueFromDisplayGroupForKey( 
259         EODisplayGroup displayGroup, String key )
260     {
261         Object value;
262         
263         if ( displayGroup.selectedObjects().size() > 1 )
264         {
265             // if there're more than one object selected, set
266             // the value to blank for all of them.
267             Object previousValue;
268 
269             Iterator indexIterator = displayGroup.selectionIndexes().
270                                       iterator();
271 
272             // get value for the first selected object.
273             int initialIndex = ( (Integer)indexIterator.next() ).intValue();
274             previousValue = displayGroup.valueForObjectAtIndex(
275                                       initialIndex, key );
276             value = null;
277 
278             // go through the rest of the selected objects, compare each
279             // value with the previous one. continue comparing if two
280             // values are equal, break the while loop if they're different.
281             // the final value will be the common value of all selected objects
282             // if there is one, or be blank if there is not.
283             while ( indexIterator.hasNext() )
284             {
285                 int index = ( (Integer)indexIterator.next() ).intValue();
286                 Object currentValue = displayGroup.valueForObjectAtIndex(
287                                       index, key );
288                 if ( currentValue != null && !currentValue.equals( previousValue ) )
289                 {
290                     value = null;
291                     break;
292                 }
293                 else
294                 {
295                     // currentValue is the same as the previous one
296                     value = currentValue;
297                 }
298 
299             } // end while
300 
301         } else {
302 
303             value = displayGroup.selectedObjectValueForKey( key );
304         } // end checking size of displayGroup
305         
306         return value;
307     }
308 	
309 	/***
310 	* Writes the value currently in the component
311 	* to the selected object in the display group
312 	* bound to the value aspect.
313     * @return false if there were problems validating,
314     * or true to continue.
315 	*/
316 	protected boolean writeValueForAspect( Object value, String aspect )
317 	{
318 		EODisplayGroup displayGroup = 
319 			displayGroupForAspect( aspect );
320 		if ( displayGroup != null )
321 		{
322 			String key = displayGroupKeyForAspect( aspect );
323 
324             boolean returnValue = true;
325             Iterator selectedIterator = displayGroup.selectionIndexes().iterator();
326             while ( selectedIterator.hasNext() )
327             {
328                 int index = ( (Integer)selectedIterator.next() ).intValue();
329 
330                 if ( !displayGroup.setValueForObjectAtIndex( value, index, key ) )
331                 {
332                     returnValue = false;
333                 }
334             }
335             return returnValue;
336 		}
337 		return false;
338 	}
339 
340     /***
341     * Forces this association to cause the object to 
342     * stop editing and validate the user's input.
343     * @return false if there were problems validating,
344     * or true to continue.
345     */
346     public boolean endEditing ()
347     {        
348         return false;
349 //!	    return writeValueToDisplayGroup();
350     }
351 	
352 }
353 
354 /*
355  * $Log$
356  * Revision 1.2  2006/02/18 23:14:35  cgruber
357  * Update imports and maven dependencies.
358  *
359  * Revision 1.1  2006/02/16 13:22:22  cgruber
360  * Check in all sources in eclipse-friendly maven-enabled packages.
361  *
362  * Revision 1.3  2003/08/06 23:07:52  chochos
363  * general code cleanup (mostly, removing unused imports)
364  *
365  * Revision 1.2  2001/11/08 19:51:24  mpowers
366  * Draft implementation.
367  *
368  * Revision 1.1  2001/11/02 23:15:04  mpowers
369  * Contributing "generic association".
370  *
371  *
372  */
373