/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Intersect Software Corporation This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.control; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.TreeSet; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSMutableArray; import net.wotonomy.foundation.internal.Introspector; import net.wotonomy.foundation.internal.WotonomyException; /** * A data source that reads and writes to an indexed * property of a java object. This class is used by * MasterDetailAssociation to retreive objects from * the master display group. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 894 $ */ public class PropertyDataSource extends OrderedDataSource { protected Object source; protected String key; protected Class lastKnownType; // for best-guessing protected EOClassDescription classDesc; protected EOEditingContext context; /** * Creates a new PropertyDataSource with no editing context * and will try to guess the appropriate class description * when trying to create objects. */ public PropertyDataSource() { this( null, (EOClassDescription) null ); } /** * Creates a new PropertyDataSource that uses the specified * editing context, but will try to guess the appropriate * class description when trying to create objects. */ public PropertyDataSource( EOEditingContext aContext ) { this( aContext, (EOClassDescription) null ); } /** * Creates a new PropertyDataSource that uses the specified * editing context and vends objects of the specified class. */ public PropertyDataSource( EOEditingContext aContext, Class aClass ) { this( aContext, EOClassDescription.classDescriptionForClass( aClass ) ); } /** * Creates a new PropertyDataSource that uses the specified * editing context and vends objects of the specified * class description. */ public PropertyDataSource( EOEditingContext aContext, EOClassDescription aClassDesc ) { source = null; key = null; lastKnownType = null; classDesc = aClassDesc; context = aContext; } /** * Provides the master object for detail display groups. */ public Object source() { return source; } /** * Allows a detail display group to set the master object. */ public void setSource( Object anObject ) { source = anObject; } /** * Provides the detail key for detail display groups. */ public String key() { return key; } /** * Allows a detail display group to set the detail key. */ public void setKey( String aKey ) { key = aKey; } /** * Inserts the specified object into this data source. * Calls insertObjectAtIndex and appends to the end * of the list. */ public void insertObject ( Object anObject ) { insertObjectAtIndex( anObject, -1 ); // trick to force to end } /** * Inserts the specified object into this data source, * at the specified index. */ public void insertObjectAtIndex ( Object anObject, int anIndex ) { if ( source == null ) return; List list = readAsList(); if ( anIndex == -1 ) anIndex = list.size(); // force to end if ( anIndex > list.size() ) anIndex = list.size(); // force to end list.add( anIndex, anObject ); writeAsList( list ); } /** * Deletes the specified object from this data source. */ public void deleteObject ( Object anObject ) { if ( source == null ) return; List list = readAsList(); list.remove( anObject ); writeAsList( list ); } public EOEditingContext editingContext () { return context; } /** * Returns a List containing the objects in this * data source. */ public NSArray fetchObjects () { if ( source == null ) return NSArray.EmptyArray; return readAsList(); } /** * Returns a new instance of this class. */ public EODataSource dataSourceQualifiedByKey ( String aKey ) { // determine the target class desc if possible EOClassDescription keyClassDesc = null; if ( classDesc != null ) { keyClassDesc = classDesc.classDescriptionForDestinationKey( aKey ); } return new PropertyDataSource( editingContext(), keyClassDesc ); } /** * Restricts this data source to vend those * objects that are associated with the specified * key on the specified object. */ public void qualifyWithRelationshipKey ( String aKey, Object anObject ) { source = anObject; key = aKey; } /** * Returns the class description passed to the * constructor, if any. If no class description and * if the bound property is an indexed property, * the type of the array is returned, otherwise * this method returns null. This method is called * by createObject(). */ public EOClassDescription classDescriptionForObjects () { // just return the class description if we have one if ( classDesc != null ) return classDesc; // otherwise, try to do some guesswork EOClassDescription result = null; // lastKnownType is not updated here Class type = lastKnownType; // if no last known type if ( type == null ) { // if source and key were specified if ( ( source != null ) && ( key != null ) ) { // try to get an array type Method m = Introspector.getPropertyReadMethod( source.getClass(), key, new Class[0] ); if ( m != null ) { Class returnType = m.getReturnType(); if ( returnType.isArray() ) { type = returnType.getComponentType(); } } else { throw new WotonomyException( "Key does not exist for object: " + key + " : " + source ); } } // does not update lastKnownType because // we prefer to get that info from a fetch. } // if type has been determined if ( type != null ) { result = EOClassDescription.classDescriptionForClass( type ); } return result; } /** * Calls getValue() and returns the result as a List. * Sets lastKnownType to the retrieved type. */ protected NSMutableArray readAsList() { Object value = getValue(); if ( value == null ) { return new NSMutableArray(); } Object o; NSMutableArray result = new NSMutableArray(); boolean hasReadType = false; lastKnownType = null; // if instance of array, convert to list if ( value.getClass().isArray() ) { int count = Array.getLength( value ); for ( int i = 0; i < count; i++ ) { o = Array.get( value, i ); if ( o != null ) { // we've already found a type if ( hasReadType ) { // check that this matches the last known type if ( o.getClass() != lastKnownType ) { // not all of the same type: set to null lastKnownType = null; } } else // this is the first type we've found { // remember it hasReadType = true; lastKnownType = o.getClass(); } } result.add( o ); } } else if ( value instanceof Collection ) { // convert to list so we handle sets, etc. Iterator i = ((Collection)value).iterator(); while ( i.hasNext() ) { o = i.next(); if ( o != null ) { // we've already found a type if ( hasReadType ) { // check that this matches the last known type if ( o.getClass() != lastKnownType ) { // not all of the same type: set to null lastKnownType = null; } } else // this is the first type we've found { // remember it hasReadType = true; lastKnownType = o.getClass(); } } result.add( o ); } } else { lastKnownType = null; throw new WotonomyException( "PropertyDataSource: " + "bound property was not an indexed property: " + key ); } return result; } /** * Converts the specified List to lastKnownType * and calls setValue(). */ protected void writeAsList( List anObjectList ) { if ( source == null ) { throw new WotonomyException( "PropertyDataSource: " + "no source object: " + key ); } Class c = source.getClass(); Method m = Introspector.getPropertyReadMethod( c, key, new Class[0] ); if ( m == null ) { throw new WotonomyException( "Could not read property for object: " + key + " : " + source + " (" + c + ")" ); } Class returnType = m.getReturnType(); int count = anObjectList.size(); Object result = null; if ( returnType.isArray() ) { Class type = returnType.getComponentType(); result = Array.newInstance( type, count ); for ( int i = 0; i < count; i++ ) { Array.set( result, i, anObjectList.get( i ) ); } } else { Collection collection = null; if ( ! returnType.isInterface() ) { try { collection = (Collection) returnType.newInstance(); } catch ( Exception exc ) { // no default constructor, leave null } } // try to find an acceptable collections type if ( collection == null ) { if ( returnType.isAssignableFrom( NSMutableArray.class ) ) { collection = new NSMutableArray(); } else if ( returnType.isAssignableFrom( LinkedList.class ) ) { collection = new LinkedList(); } else if ( returnType.isAssignableFrom( ArrayList.class ) ) { collection = new ArrayList(); } else if ( returnType.isAssignableFrom( HashSet.class ) ) { collection = new HashSet(); } else if ( returnType.isAssignableFrom( TreeSet.class ) ) { collection = new TreeSet(); } } if ( collection == null ) { throw new WotonomyException( "Could not create a collection of type: " + returnType ); } collection.addAll( anObjectList ); result = collection; } setValue( result ); } /** * Returns the value of the indexed property * specified by qualifyWithRelationshipKey. */ protected Object getValue() { if ( source instanceof EOKeyValueCoding ) { return ((EOKeyValueCoding)source).valueForKey( key ); } return EOKeyValueCodingSupport.valueForKey( source, key ); } /** * Sets the value of the indexed property * specified by qualifyWithRelationshipKey. * The argument is assumed to be of appropriate * type for the property. EOObserverCenter is * notified that the object will change. */ protected void setValue( Object aValue ) { EOClassDescription sourceDesc = EOClassDescription.classDescriptionForClass( source.getClass() ); // if we're not editing a relationship (?) // if ( ! sourceDesc.toManyRelationshipKeys().containsObject( key ) ) { // mark the parent as changed EOObserverCenter.notifyObserversObjectWillChange( source ); } if ( source instanceof EOKeyValueCoding ) { ((EOKeyValueCoding)source).takeValueForKey( aValue, key ); } else { EOKeyValueCodingSupport.takeValueForKey( source, aValue, key ); } } } /* * $Log$ * Revision 1.2 2006/02/16 16:47:14 cgruber * Move some classes in to "internal" packages and re-work imports, etc. * * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. * * Revision 1.1 2006/02/16 13:19:57 cgruber * Check in all sources in eclipse-friendly maven-enabled packages. * * Revision 1.14 2003/01/18 23:30:42 mpowers * WODisplayGroup now compiles. * * Revision 1.13 2002/10/24 21:15:36 mpowers * New implementations of NSArray and subclasses. * * Revision 1.12 2002/10/24 18:18:12 mpowers * NSArray's are now considered read-only, so we can return our internal * representation to reduce unnecessary object allocation. * * Revision 1.11 2002/04/15 21:55:33 mpowers * Catching a condition where the get may not return the value passed to set. * * Revision 1.10 2002/03/08 23:20:37 mpowers * insertObject now calls insertObjectAtIndex. * * Revision 1.9 2001/06/05 19:10:41 mpowers * Better handling of null properties. * * Revision 1.8 2001/05/21 14:03:35 mpowers * Added a convenience constructor for java classes. * * Revision 1.7 2001/04/30 13:15:24 mpowers * Child contexts re-initializing objects invalidated in parent now * propery transpose relationships. * * Revision 1.6 2001/04/29 02:29:31 mpowers * Debugging relationship faulting. * * Revision 1.5 2001/04/28 22:17:51 mpowers * Revised PropertyDataSource to be EOClassDescription-aware. * * Revision 1.4 2001/04/27 23:37:20 mpowers * Now using EOClassDescription in the EODataSource class, as we should. * * Revision 1.3 2001/03/29 03:29:49 mpowers * Now using KeyValueCoding and Support instead of Introspector. * * Revision 1.2 2001/01/24 14:10:53 mpowers * Contributing OrderedDataSource, and PropertyDataSource extends it. * * Revision 1.1.1.1 2000/12/21 15:46:50 mpowers * Contributing wotonomy. * * Revision 1.3 2000/12/20 16:25:35 michael * Added log to all files. * * */