View Javadoc

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.control;
20  
21  import java.lang.reflect.Array;
22  import java.lang.reflect.Method;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.HashSet;
26  import java.util.Iterator;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.TreeSet;
30  
31  import net.wotonomy.foundation.NSArray;
32  import net.wotonomy.foundation.NSMutableArray;
33  import net.wotonomy.foundation.internal.Introspector;
34  import net.wotonomy.foundation.internal.WotonomyException;
35  
36  /***
37  * A data source that reads and writes to an indexed 
38  * property of a java object. This class is used by 
39  * MasterDetailAssociation to retreive objects from 
40  * the master display group.  
41  *
42  * @author michael@mpowers.net
43  * @author $Author: cgruber $
44  * @version $Revision: 894 $
45  */
46  public class PropertyDataSource extends OrderedDataSource
47  {
48      protected Object source;
49      protected String key;
50      protected Class lastKnownType; // for best-guessing
51      protected EOClassDescription classDesc;
52      protected EOEditingContext context;
53  
54      /***
55      * Creates a new PropertyDataSource with no editing context
56      * and will try to guess the appropriate class description
57      * when trying to create objects.
58      */
59      public PropertyDataSource()
60      {
61          this( null, (EOClassDescription) null );
62      }
63      
64      /***
65      * Creates a new PropertyDataSource that uses the specified
66      * editing context, but will try to guess the appropriate
67      * class description when trying to create objects.
68      */
69      public PropertyDataSource( EOEditingContext aContext )
70      {
71          this( aContext, (EOClassDescription) null );   
72      }
73  
74      /***
75      * Creates a new PropertyDataSource that uses the specified
76      * editing context and vends objects of the specified class.
77      */
78      public PropertyDataSource( 
79          EOEditingContext aContext, Class aClass )
80      {
81          this( aContext, EOClassDescription.classDescriptionForClass( aClass ) );
82      }
83  
84      /***
85      * Creates a new PropertyDataSource that uses the specified
86      * editing context and vends objects of the specified
87      * class description.
88      */
89      public PropertyDataSource( 
90          EOEditingContext aContext, EOClassDescription aClassDesc )
91      {
92          source = null;
93          key = null;
94          lastKnownType = null;
95          classDesc = aClassDesc;
96          context = aContext;
97      }
98      
99      /***
100     * Provides the master object for detail display groups.
101     */
102     public Object source()
103     {
104         return source;
105     }
106     
107     /***
108     * Allows a detail display group to set the master object.
109     */
110     public void setSource( Object anObject )
111     {
112         source = anObject;
113     }
114     
115     /***
116     * Provides the detail key for detail display groups.
117     */
118     public String key()
119     {
120         return key;
121     }
122 
123     /***
124     * Allows a detail display group to set the detail key.
125     */
126     public void setKey( String aKey )
127     {
128         key = aKey;
129     }
130 
131     /***
132     * Inserts the specified object into this data source.
133     * Calls insertObjectAtIndex and appends to the end 
134     * of the list.
135     */
136     public void insertObject ( Object anObject )
137     {
138         insertObjectAtIndex( anObject, -1 ); // trick to force to end
139     }
140 
141     /***
142     * Inserts the specified object into this data source,
143     * at the specified index.
144     */
145     public void insertObjectAtIndex ( 
146         Object anObject, int anIndex )
147     {
148         if ( source == null ) return;
149         List list = readAsList();
150         if ( anIndex == -1 ) anIndex = list.size(); // force to end
151         if ( anIndex > list.size() ) anIndex = list.size(); // force to end
152         list.add( anIndex, anObject );
153         writeAsList( list );
154     }
155 
156     /***
157     * Deletes the specified object from this data source.
158     */
159     public void deleteObject ( Object anObject )
160     {
161         if ( source == null ) return;
162         List list = readAsList();
163         list.remove( anObject );
164         writeAsList( list );
165     }
166 
167     public EOEditingContext editingContext ()
168     {
169         return context;
170     }
171 
172     /***
173     * Returns a List containing the objects in this
174     * data source.  
175     */
176     public NSArray fetchObjects ()
177     {
178         if ( source == null ) return NSArray.EmptyArray;
179         return readAsList(); 
180     }
181 
182     /***
183     * Returns a new instance of this class.
184     */
185     public EODataSource 
186         dataSourceQualifiedByKey ( String aKey )
187     {
188         // determine the target class desc if possible
189         EOClassDescription keyClassDesc = null;
190         if ( classDesc != null )
191         {
192             keyClassDesc = classDesc.classDescriptionForDestinationKey( aKey );
193         }
194         return new PropertyDataSource( editingContext(), keyClassDesc );
195     }
196 
197     /***
198     * Restricts this data source to vend those 
199     * objects that are associated with the specified 
200     * key on the specified object.
201     */
202     public void 
203         qualifyWithRelationshipKey ( 
204         String aKey, Object anObject )
205     {
206         source = anObject;
207         key = aKey;
208     }
209 
210     /***
211     * Returns the class description passed to the 
212     * constructor, if any.  If no class description and
213     * if the bound property is an indexed property,
214     * the type of the array is returned, otherwise
215     * this method returns null.  This method is called
216     * by createObject().
217     */
218     public EOClassDescription 
219         classDescriptionForObjects ()
220     {
221         // just return the class description if we have one
222         if ( classDesc != null ) return classDesc;
223         
224         // otherwise, try to do some guesswork
225         EOClassDescription result = null;
226         
227         // lastKnownType is not updated here
228         Class type = lastKnownType;
229 
230         // if no last known type        
231         if ( type == null )
232         {
233             // if source and key were specified
234             if ( ( source != null ) && ( key != null ) )
235             {
236                 // try to get an array type
237                 Method m = Introspector.getPropertyReadMethod(
238                     source.getClass(), key, new Class[0] );
239                 if ( m != null )
240                 {
241                     Class returnType = m.getReturnType();
242                     if ( returnType.isArray() )
243                     {
244                         type = returnType.getComponentType();
245                     }
246                 }
247                 else
248                 {
249                     throw new WotonomyException( "Key does not exist for object: " + key + " : " + source );   
250                 }
251             }
252             
253             // does not update lastKnownType because
254             // we prefer to get that info from a fetch.
255         }
256         
257         // if type has been determined
258         if ( type != null ) 
259         {
260             result = 
261                 EOClassDescription.classDescriptionForClass( type );
262         }
263         
264         return result;
265     }
266     
267     /***
268     * Calls getValue() and returns the result as a List.
269     * Sets lastKnownType to the retrieved type.
270     */
271     protected NSMutableArray readAsList()
272     {
273         Object value = getValue();
274         if ( value == null )
275         {
276             return new NSMutableArray();
277         }
278         
279         Object o;
280         NSMutableArray result = new NSMutableArray();
281         boolean hasReadType = false;
282         lastKnownType = null;
283         
284 		// if instance of array, convert to list 
285 		if ( value.getClass().isArray() )
286 		{
287 			int count = Array.getLength( value );
288 			for ( int i = 0; i < count; i++ )
289 			{
290                 o = Array.get( value, i );
291                 if ( o != null )
292                 {
293                     // we've already found a type
294                     if ( hasReadType ) 
295                     {
296                         // check that this matches the last known type
297                         if ( o.getClass() != lastKnownType )
298                         {
299                             // not all of the same type: set to null
300                             lastKnownType = null;
301                         }
302                     }
303                     else // this is the first type we've found
304                     {
305                         // remember it
306                         hasReadType = true;
307                         lastKnownType = o.getClass();
308                     }
309                 }
310 				result.add( o );	
311 			}
312 		}
313 		else
314 		if ( value instanceof Collection )
315 		{
316 			// convert to list so we handle sets, etc.
317             Iterator i = ((Collection)value).iterator();
318             while ( i.hasNext() )
319             {
320                 o = i.next();   
321                 if ( o != null )
322                 {
323                     // we've already found a type
324                     if ( hasReadType ) 
325                     {
326                         // check that this matches the last known type
327                         if ( o.getClass() != lastKnownType )
328                         {
329                             // not all of the same type: set to null
330                             lastKnownType = null;
331                         }
332                     }
333                     else // this is the first type we've found
334                     {
335                         // remember it
336                         hasReadType = true;
337                         lastKnownType = o.getClass();
338                     }
339                 }
340                 result.add( o );
341             }
342 		}
343 		else
344 		{
345             lastKnownType = null;
346 			throw new WotonomyException( "PropertyDataSource: " +
347 				"bound property was not an indexed property: " + key );
348 		}
349 
350         return result;
351     }
352     
353     /***
354     * Converts the specified List to lastKnownType
355     * and calls setValue().
356     */
357     protected void writeAsList( List anObjectList )
358     {
359         if ( source == null )
360         {
361             throw new WotonomyException( "PropertyDataSource: " +
362                 "no source object: " + key );
363         }
364         
365         Class c = source.getClass();
366         Method m = Introspector.getPropertyReadMethod( c, key, new Class[0] );
367         if ( m == null )
368         {
369             throw new WotonomyException( "Could not read property for object: " 
370                 + key + " : " + source + " (" + c + ")" );
371         }
372 
373         Class returnType = m.getReturnType();
374 
375         int count = anObjectList.size();
376         Object result = null;
377         
378         if ( returnType.isArray() )
379         {
380             Class type = returnType.getComponentType();
381             result = Array.newInstance( type, count );
382             for ( int i = 0; i < count; i++ )
383             {
384                 Array.set( result, i, anObjectList.get( i ) );
385             }
386         }
387         else
388         {
389             Collection collection = null;
390             
391             if ( ! returnType.isInterface() )
392             {
393                 try
394                 {
395                     collection = (Collection) returnType.newInstance();
396                 }
397                 catch ( Exception exc )
398                 {
399                     // no default constructor, leave null
400                 }
401             }
402             
403             // try to find an acceptable collections type
404             if ( collection == null )
405             {
406                 if ( returnType.isAssignableFrom( NSMutableArray.class ) )
407                 {
408                     collection = new NSMutableArray();
409                 }
410                 else
411                 if ( returnType.isAssignableFrom( LinkedList.class ) )
412                 {
413                     collection = new LinkedList();
414                 }
415                 else
416                 if ( returnType.isAssignableFrom( ArrayList.class ) )
417                 {
418                     collection = new ArrayList();
419                 }
420                 else
421                 if ( returnType.isAssignableFrom( HashSet.class ) )
422                 {
423                     collection = new HashSet();
424                 }
425                 else
426                 if ( returnType.isAssignableFrom( TreeSet.class ) )
427                 {
428                     collection = new TreeSet();
429                 }
430             }
431             
432             if ( collection == null )
433             {
434                 throw new WotonomyException( "Could not create a collection of type: " + returnType );   
435             }
436             
437             collection.addAll( anObjectList );
438             result = collection;
439         }
440         
441         setValue( result );
442     }
443     
444     /***
445     * Returns the value of the indexed property
446     * specified by qualifyWithRelationshipKey.
447     */
448     protected Object getValue()
449     {
450         if ( source instanceof EOKeyValueCoding )
451         {
452             return ((EOKeyValueCoding)source).valueForKey( key );   
453         }
454         return EOKeyValueCodingSupport.valueForKey( source, key );
455     }
456     
457     /***
458     * Sets the value of the indexed property
459     * specified by qualifyWithRelationshipKey.
460     * The argument is assumed to be of appropriate
461     * type for the property.  EOObserverCenter is
462     * notified that the object will change.
463     */
464     protected void setValue( Object aValue )
465     {
466         EOClassDescription sourceDesc = 
467             EOClassDescription.classDescriptionForClass( source.getClass() );
468             
469             
470         // if we're not editing a relationship (?)
471 //        if ( ! sourceDesc.toManyRelationshipKeys().containsObject( key ) )
472         {
473             // mark the parent as changed
474             EOObserverCenter.notifyObserversObjectWillChange( source );
475         }
476 
477         
478         if ( source instanceof EOKeyValueCoding )
479         {
480             ((EOKeyValueCoding)source).takeValueForKey( aValue, key );   
481         }
482         else
483         {
484             EOKeyValueCodingSupport.takeValueForKey( source, aValue, key );
485         }
486     }
487     
488 }
489 
490 /*
491  * $Log$
492  * Revision 1.2  2006/02/16 16:47:14  cgruber
493  * Move some classes in to "internal" packages and re-work imports, etc.
494  *
495  * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions.
496  *
497  * Revision 1.1  2006/02/16 13:19:57  cgruber
498  * Check in all sources in eclipse-friendly maven-enabled packages.
499  *
500  * Revision 1.14  2003/01/18 23:30:42  mpowers
501  * WODisplayGroup now compiles.
502  *
503  * Revision 1.13  2002/10/24 21:15:36  mpowers
504  * New implementations of NSArray and subclasses.
505  *
506  * Revision 1.12  2002/10/24 18:18:12  mpowers
507  * NSArray's are now considered read-only, so we can return our internal
508  * representation to reduce unnecessary object allocation.
509  *
510  * Revision 1.11  2002/04/15 21:55:33  mpowers
511  * Catching a condition where the get may not return the value passed to set.
512  *
513  * Revision 1.10  2002/03/08 23:20:37  mpowers
514  * insertObject now calls insertObjectAtIndex.
515  *
516  * Revision 1.9  2001/06/05 19:10:41  mpowers
517  * Better handling of null properties.
518  *
519  * Revision 1.8  2001/05/21 14:03:35  mpowers
520  * Added a convenience constructor for java classes.
521  *
522  * Revision 1.7  2001/04/30 13:15:24  mpowers
523  * Child contexts re-initializing objects invalidated in parent now
524  * propery transpose relationships.
525  *
526  * Revision 1.6  2001/04/29 02:29:31  mpowers
527  * Debugging relationship faulting.
528  *
529  * Revision 1.5  2001/04/28 22:17:51  mpowers
530  * Revised PropertyDataSource to be EOClassDescription-aware.
531  *
532  * Revision 1.4  2001/04/27 23:37:20  mpowers
533  * Now using EOClassDescription in the EODataSource class, as we should.
534  *
535  * Revision 1.3  2001/03/29 03:29:49  mpowers
536  * Now using KeyValueCoding and Support instead of Introspector.
537  *
538  * Revision 1.2  2001/01/24 14:10:53  mpowers
539  * Contributing OrderedDataSource, and PropertyDataSource extends it.
540  *
541  * Revision 1.1.1.1  2000/12/21 15:46:50  mpowers
542  * Contributing wotonomy.
543  *
544  * Revision 1.3  2000/12/20 16:25:35  michael
545  * Added log to all files.
546  *
547  *
548  */
549