[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[RFA/gdl2] EOKeyValueCoding
From: |
David Ayers |
Subject: |
[RFA/gdl2] EOKeyValueCoding |
Date: |
Mon, 30 Jun 2003 18:06:01 +0200 |
User-agent: |
Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.4b) Gecko/20030507 |
Hello Manuel,
I'd like your approval on this one. This is my first cleanup of
EOKeyValueCoding. I've been using this implementation more or less
since February, but I haven't consciously used some of GDL2 extensions
(well only in simple tests). What this does:
- Relies on -base/Foundation's implementation for all equivalent
implementations.
- Replaces -base/Foundation's implementation of unableToSetNilForKey: to
call unableToSetNullForKey:
- Optimizes NSArray computational implementations to use NSDecmial
structs instead of temporary NSDecimalNumber objects
- Cleans up serveral implementations.
- Adds documentation
I currently left the special handling of quoted keys in the NSDictionary
catagory, eventhough it can be turned off with the
GSUseStrictWO451Compatibility default. I'm hoping to find time to
implement a GSWMutableDictionary to be used in the GSWDisplayGroup
sometime, so that we can move that handling there.
This shouldn't change (well only correct some edge cases) the current
behavior. I have run simple tests on all the methods. But I would like
you to test it against your app before I commit.
The following questions remain:
-smartTakeValueForKey: seems to be hooks to support EOGenericRecord
which consults the relationship definitions to send the
add/removeObject:to/fromBothSidesOfRelationshipWithKey: messages instead
of takeValue:forKey: if applicable. Why is this specific to
EOGenericRecord and not implemented for custom objects?
-smartTakeValueForKey: is currently only used in
GSWBundle-initializeObject:fromArchive: and
GSWComponent-synchronizeParentToComponent and no where in GDL2 itself.
I'm wondering whether we really need it at all.
-smartTakeValueForKeyPath: is currently only used in
GSWAssociation-setValue:inObject:forKeyPath: and this already contiains
an alternative implementation when GDL2 isn't available. I'd rather
move the logic to GSWAssociation and remove smartTakeValueForKeyPath:
I also have the feeling that we could do without
takeStoredValueForKeyPath: and storedValueForKeyPath:. They both use
valueForKey recursively (and why not storedValueForKey?) until the get
to the object before the targeted object, and then send the
corresponding stored KVC message. Yet these meothods are used neither
in GDL2 nor in GSWeb.
valuesForKeyPaths: and storedValuesForKeyPaths: are also seem unused
and I think we should also remove them.
Again I've added the files instead of a diff to make reviewal easier.
Cheers,
David
/*
EOKeyValueCoding.h
Copyright (C) 2000 Free Software Foundation, Inc.
Author: Mirko Viviani <mirko.viviani@rccr.cremona.it>
Date: February 2000
Modified: David Ayers <d.ayers@inode.at>
Date: February 2003
This file is part of the GNUstep Database Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef __EOKeyValueCoding_h__
#define __EOKeyValueCoding_h__
#ifndef NeXT_Foundation_LIBRARY
#include <Foundation/NSObject.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSKeyValueCoding.h>
#include <Foundation/NSDictionary.h>
#else
#include <Foundation/Foundation.h>
#endif
#include "EODefines.h"
/**
* GDL2 aims to be compatible with EOF of WebObjects 4.5 and expects to be
* compiled with gnustep-base and the current version of Mac OS X.
* As many of the EOKeyValueCoding methods have moved to NSKeyValueCoding,
* GDL2 merely implements those methods which are not part of NSKeyValueCoding
* or reimplements those methods to insure WebObjects 4.5 compatibility or
* augment the behavior for GDL2 specific features.
*/
@interface NSObject (EOKeyValueCoding)
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (id)valueForKey: (NSString *)key;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (void)takeValue: (id)value forKey: (NSString *)key;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (id)storedValueForKey: (NSString *)key;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (void)takeStoredValue: (id)value forKey: (NSString *)key;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
+ (BOOL)accessInstanceVariablesDirectly;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
+ (BOOL)useStoredAccessor;
/**
* Does nothing. Key bindings are currently not cached so there is no
* need to flush them. This method exists for API compatibility.
*/
+ (void)flushAllKeyBindings;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (id)handleQueryWithUnboundKey: (NSString *)key;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (void)handleTakeValue: (id)value forUnboundKey: (NSString *)key;
/**
* This method is invoked by the EOKeyValueCoding mechanism when an attempt
* is made to set an null value for a scalar attribute. This implementation
* raises an NSInvalidArgument exception. <br/>
* The NSKeyValueCoding -unableToSetNilForKey: is overriden to invoke this
* method instead. We manipulate the runtime to insure that our implementation
* of unableToSetNilForKey: is used in favor of the one in gnustep-base or
* Foundation.
*/
- (void)unableToSetNullForKey: (NSString *)key;
@end
@interface NSObject (EOKeyValueCodingAdditions)
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (id)valueForKeyPath: (NSString *)keyPath;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (void)takeValue: (id)value forKeyPath: (NSString *)keyPath;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (NSDictionary *)valuesForKeys: (NSArray *)keys;
/**
* Unimplemented here. Relies on NSKeyValueCoding.
*/
- (void)takeValuesFromDictionary: (NSDictionary *)dictionary;
@end
@interface NSArray (EOKeyValueCoding)
- (id)valueForKey: (NSString *)key;
- (id)valueForKeyPath: (NSString *)keyPath;
- (id)computeSumForKey: (NSString *)key;
- (id)computeAvgForKey: (NSString *)key;
- (id)computeCountForKey: (NSString *)key;
- (id)computeMaxForKey: (NSString *)key;
- (id)computeMinForKey: (NSString *)key;
@end
@interface NSDictionary (EOKeyValueCoding)
/*
* Overrides gnustep-base and Foundations implementation.
* See documentation or source file for details on how it differs.
*/
- (id)valueForKey: (NSString *)key;
- (id)storedValueForKey: (NSString *)key;
- (id)valueForKeyPath: (NSString *)keyPath;
- (id)storedValueForKeyPath: (NSString*)keyPath;
@end
@interface NSMutableDictionary (EOKeyValueCoding)
/*
* Overrides gnustep-base and Foundations implementation.
* See documentation or source file for details on how it differs.
*/
- (void)takeValue: (id)value
forKey: (NSString*)key;
@end
@interface NSObject (EOKVCGDL2Additions)
/* These are hooks for EOGenericRecord KVC implementaion. */
- (void)smartTakeValue: (id)anObject
forKey: (NSString *)aKey;
- (void)smartTakeValue: (id)anObject
forKeyPath: (NSString *)aKeyPath;
- (void)takeStoredValue: value
forKeyPath: (NSString *)key;
- (id)storedValueForKeyPath: (NSString *)key;
- (NSDictionary *)valuesForKeyPaths: (NSArray *)keyPaths;
- (NSDictionary *)storedValuesForKeyPaths: (NSArray *)keyPaths;
@end
#define EOUnknownKeyException NSUnknownKeyException;
GDL2CONTROL_EXPORT NSString *EOTargetObjectUserInfoKey;
GDL2CONTROL_EXPORT NSString *EOUnknownUserInfoKey;
/*
* The following declaration is reportedly missing in Apple's headers,
* yet are implemented.
*/
#if NeXT_Foundation_LIBRARY
@interface NSObject (MacOSX)
- (void)takeStoredValuesFromDictionary: (NSDictionary *)dictionary;
@end
#endif
#endif /* __EOKeyValueCoding_h__ */
/**
EOKeyValueCoding.m <title>EOKeyValueCoding</title>
Copyright (C) 1996-2002, 2003 Free Software Foundation, Inc.
Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
Date: November 1996
Author: Mirko Viviani <mirko.viviani@rccr.cremona.it>
Date: February 2000
Author: Manuel Guesdon <mguesdon@oxymium.net>
Date: January 2002
Author: David Ayers <d.ayers@inode.at>
Date: February 2003
$Revision: 1.11 $
$Date: 2003/05/02 13:53:05 $
<abstract></abstract>
This file is part of the GNUstep Database Library.
<license>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; see the file COPYING.LIB.
If not, write to the Free Software Foundation,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
</license>
**/
#include "config.h"
RCS_ID("$Id: EOKeyValueCoding.m,v 1.11 2003/05/02 13:53:05 ayers Exp $")
#ifndef NeXT_Foundation_LIBRARY
#include <Foundation/NSObject.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSHashTable.h>
#include <Foundation/NSException.h>
#include <Foundation/NSObjCRuntime.h>
#include <Foundation/NSMapTable.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSDecimalNumber.h>
#include <Foundation/NSDebug.h>
#else
#include <Foundation/Foundation.h>
#endif
#include <EOControl/EOKeyValueCoding.h>
#include <EOControl/EONSAddOns.h>
#include <EOControl/EODebug.h>
#include <EOControl/EONull.h>
#include <gnustep/base/GSObjCRuntime.h>
static EONull *null = nil;
static SEL oaiSel;
static BOOL strictWO;
static inline void
initialize(void)
{
if (null == nil)
{
null = [EONull null];
oaiSel = @selector(objectAtIndex:);
strictWO = GSUseStrictWO451Compatibility(nil);
}
}
/* This macro is only used locally in defined places so for the sake
of efficiency, we don't use the do {} while (0) pattern. */
#define INITIALIZE if (null == nil) initialize();
/*
* This dummy class exists to provide a replacement implementation for
* NSObject -unableToSetNilForKey:, which calls -unableToSetNullForKey:
* as defined in WO4.5. We need this mechanism as a category cannot
* reliably override the category in gnustep-base or Foundation.
*/
@interface NilToNull : NSObject
@end
@interface NilToNull (SurrpressWarning)
- (void) unableToSetNullForKey: (NSString *)key;
@end
@implementation NilToNull
+ (void)load
{
Class cls;
SEL sel;
IMP imp;
GSMethod method;
imp = NULL;
sel = @selector(unableToSetNilForKey:);
cls = GSClassFromName("NSObject");
method = GSGetInstanceMethodNotInherited(self, sel);
if (method != METHOD_NULL)
{
imp = method->method_imp;
}
else
{
fprintf(stderr,
"%s: Could not find method unableToSetNilForKey: in NilToNil!\n",
__FILE__);
abort();
}
method = GSGetInstanceMethodNotInherited(cls, sel);
if (method != METHOD_NULL)
{
method->method_imp = imp;
}
else
{
fprintf(stderr,
"%s: Could not find method unableToSetNilForKey: in NSObject!\n",
__FILE__);
abort();
}
GSFlushMethodCacheForClass(cls);
}
- (void) unableToSetNilForKey: (NSString *)key
{
[self unableToSetNullForKey: key];
}
@end
@implementation NSObject (_EOKeyValueCodingCompatibility)
/* See EODeprecated.h. */
+ (void) flushClassKeyBindings
{
}
/* See header file for documentation. */
+ (void) flushAllKeyBindings
{
}
/* See header file for documentation. */
- (void) unableToSetNullForKey: (NSString *)key
{
[NSException raise: NSInvalidArgumentException
format: @"%@ -- %@ 0x%x: Given nil value to set for key \"%@\"",
NSStringFromSelector(_cmd), NSStringFromClass([self class]),
self, key];
}
@end
@implementation NSArray (EOKeyValueCoding)
/**
* EOKeyValueCoding protocol<br/>
* This overrides NSObjects implementation of this method.
* Generally this method returns an array of objects
* returned by invoking [NSObject-valueForKey:]
* for each item in the receiver, substituting EONull for nil.
* Keys formated like "@function.someKey" are resolved by invoking
* [NSArray-computeFuncionWithKey:] "someKey" on the reviever.
* The following functions are supported by default:
* <list>
* <item>@sum -> -computeSumForKey:</item>
* <item>@avg -> -computeAvgForKey:</item>
* <item>@max -> -computeMaxForKey:</item>
* <item>@min -> -computeMinForKey:</item>
* <item>@count -> -computeCountForKey:</item>
* </list>
* As a special case the @count function does not require a key,
* in fact, any key supplied is ignored.
* As another special case the key "count" is not forwarded to each object
* of the receiver but returns the number of objects of the receiver.<br/>
* There is no special handling of EONull. Therefore expect exceptions
* on EONull not responding to decimalValue and compare: when the are
* used with this mechanism.
*/
- (id)valueForKey: (NSString *)key
{
id result;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
if ([key isEqualToString: @"count"] || [key isEqualToString: @"@count"])
{
result = [NSDecimalNumber numberWithUnsignedInt: [self count]];
}
else if ([key hasPrefix:@"@"])
{
NSString *selStr;
SEL sel;
NSRange r;
r = [key rangeOfString:@"."];
NSAssert(r.location!=NSNotFound,
@"Invalid computational key structure");
r.length = r.location - 1; /* set length of key (w/o @) */
r.location = 1; /* remove leading '@' */
selStr = [NSString stringWithFormat: @"compute%@ForKey:",
[[key substringWithRange: r] capitalizedString]];
sel = NSSelectorFromString(selStr);
NSAssert(sel!=NULL,@"Invalid computational key");
result = [self performSelector: sel /* skip located '.' */
withObject: [key substringFromIndex: NSMaxRange(r) + 1]];
}
else
{
result = [self resultsOfPerformingSelector: @selector(valueForKey:)
withObject: key
defaultResult: null];
}
EOFLOGObjectFnStopCond(@"EOKVC");
return result;
}
/**
* EOKeyValueCoding protocol<br/>
* Returns the object returned by invoking [NSObject-valueForKeyPath:]
* on the object returned by invoking [NSObject-valueForKey:]
* on the reciever with the first key component supplied by the key path,
* with rest of the key path.<br/>
* If the first component starts with "@", the first component includes the key
* of the computational key component and as the form "@function.key".
* If there is only one key component, this method invokes
* [NSObject-valueForKey:] in the receiver with that component.
* All computational components are expected to specifiy a key with the
* exception of @count, in which case the key maybe omitted.
* Unlike the reference implementation GDL2 allows you to continue the keyPath
* in a meaningfull way after @count but the path must then contain a key as
* the computational key structure implies.
* (i.e. you may use "@count.self.decimalValue") The actual key "self" is
* infact ignored during the computation, but the formal structure must be
* maintained.<br/>
* It should be mentioned that the reference implementation
* would return the result of "@count" independent
* of any additional key paths, even if they were meaningless like
* "@count.bla.strange". GDL2 will raise, if the object returned by
* valueForKey:@"count.bla" (which generally is an NSDecimalNumber) raises on
* valueForKey:@"strange".
*/
- (id)valueForKeyPath: (NSString *)keyPath
{
NSRange r;
id result;
EOFLOGObjectFnStartCond(@"EOKVC");
r = [keyPath rangeOfString: @"."];
if ([keyPath hasPrefix: @"@"] == YES &&
[keyPath isEqualToString: @"@count"] == NO)
{
NSRange rr;
unsigned length;
length = [keyPath length];
NSAssert1(r.location!=NSNotFound && length > r.location,
@"invalid computational keyPath:%@", keyPath);
rr.location = NSMaxRange(r);
rr.length = length - rr.location;
r = [keyPath rangeOfString: @"."
options: 0
range: rr];
}
if (r.length == 0)
{
result = [self valueForKey: keyPath];
}
else
{
NSString *key = [keyPath substringToIndex: r.location];
NSString *path = [keyPath substringFromIndex: NSMaxRange(r)];
result = [[self valueForKey: key] valueForKeyPath: path];
}
EOFLOGObjectFnStopCond(@"EOKVC");
return result;
}
/**
* Iterates over the objects of the receiver send each object valueForKey:
* with the parameter. The decimalValue of the returned object is accumalted.
* An empty array returns NSDecimalNumber 0.
*/
- (id)computeSumForKey: (NSString *)key
{
NSDecimalNumber *ret;
NSDecimal result, left, right;
NSRoundingMode mode;
unsigned int i, count;
IMP oai;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
mode = [[NSDecimalNumber defaultBehavior] roundingMode];
oai = [self methodForSelector: oaiSel];
count = [self count];
NSDecimalFromComponents(&result, 0, 0, NO);
for (i=0; i<count; i++)
{
left = result;
right = [[(*oai)(self, oaiSel, i) valueForKey: key] decimalValue];
NSDecimalAdd(&result, &left, &right, mode);
}
ret = [NSDecimalNumber decimalNumberWithDecimal: result];
EOFLOGObjectFnStopCond(@"EOKVC");
return ret;
}
/**
* Iterates over the objects of the receiver send each object valueForKey:
* with the parameter. The decimalValue of the returned object is accumalted
* and then divided by number of objects contained by the receiver as returned
* by [NSArray-coung]. An empty array returns NSDecimalNumber 0.
*/
- (id)computeAvgForKey: (NSString *)key
{
NSDecimalNumber *ret;
NSDecimal result, left, right;
NSRoundingMode mode;
unsigned int i, count;
IMP oai;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
mode = [[NSDecimalNumber defaultBehavior] roundingMode];
oai = [self methodForSelector: oaiSel];
count = [self count];
NSDecimalFromComponents(&result, 0, 0, NO);
for (i=0; i<count; i++)
{
left = result;
right = [[(*oai)(self, oaiSel, i) valueForKey: key] decimalValue];
NSDecimalAdd(&result, &left, &right, mode);
}
left = result;
NSDecimalFromComponents(&right, (unsigned long long) count, 0, NO);
NSDecimalDivide(&result, &left, &right, mode);
ret = [NSDecimalNumber decimalNumberWithDecimal: result];
EOFLOGObjectFnStopCond(@"EOKVC");
return ret;
}
- (id)computeCountForKey: (NSString *)key
{
id result;
EOFLOGObjectFnStartCond(@"EOKVC");
result = [NSDecimalNumber numberWithUnsignedInt: [self count]];
EOFLOGObjectFnStopCond(@"EOKVC");
return result;
}
- (id)computeMaxForKey: (NSString *)key
{
id result, resultVal;
unsigned int i, count;;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
result = nil;
resultVal = nil;
count = [self count];
if (count > 0)
{
id current,currentVal;
IMP oai;
oai = [self methodForSelector: oaiSel];
for(i=0; i<count && (resultVal == nil || resultVal == null); i++)
{
result = (*oai)(self, oaiSel, i);
resultVal = [result valueForKey: key];
}
for (; i<count; i++)
{
current = (*oai)(self, oaiSel, i);
currentVal = [current valueForKey: key];
if (currentVal == nil || currentVal == null) continue;
if ([resultVal compare: currentVal] == NSOrderedAscending)
{
result = current;
resultVal = currentVal;
}
}
}
EOFLOGObjectFnStopCond(@"EOKVC");
return result;
}
- (id)computeMinForKey: (NSString *)key
{
id result, resultVal;
unsigned int i, count;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
result = nil;
resultVal = nil;
count = [self count];
if (count > 0)
{
id current, currentVal;
IMP oai;
oai = [self methodForSelector: oaiSel];
for(i=0; i<count && (resultVal == nil || resultVal == null); i++)
{
result = (*oai)(self, oaiSel, i);
resultVal = [result valueForKey: key];
}
for (; i<count; i++)
{
current = (*oai)(self, oaiSel, i);
currentVal = [current valueForKey: key];
if (currentVal == nil || currentVal == null) continue;
if ([resultVal compare: currentVal] == NSOrderedDescending)
{
result = current;
resultVal = currentVal;
}
}
}
EOFLOGObjectFnStopCond(@"EOKVC");
return result;
}
@end
@implementation NSDictionary (EOKeyValueCoding)
/**
* Returns the object stored in the dictionary for this key.
* Unlike Foundation, this method may return objects for keys other than
* those explicitly stored in the receiver. These special keys are
* 'count', 'allKeys' and 'allValues'.
* We override the implementation to account for these
* special keys.
*/
- (id)valueForKey: (NSString *)key
{
id value;
EOFLOGObjectFnStartCond(@"EOKVC");
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@",
// key);
value = [self objectForKey: key];
if (!value)
{
if ([key isEqualToString: @"allValues"])
{
value = [self allValues];
}
else if ([key isEqualToString: @"allKeys"])
{
value = [self allKeys];
}
else if ([key isEqualToString: @"count"])
{
value = [NSNumber numberWithUnsignedInt: [self count]];
}
}
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@ value: %p (class=%@)",
// key, value, [value class]);
EOFLOGObjectFnStopCond(@"EOKVC");
return value;
}
/**
* Returns the object stored in the dictionary for this key.
* Unlike Foundation, this method may return objects for keys other than
* those explicitly stored in the receiver. These special keys are
* 'count', 'allKeys' and 'allValues'.
* We do not simply invoke [NSDictionary-valueForKey:]
* to avoid recursions in subclasses that might implement
* [NSDictionary-valueForKey:] by calling [NSDictionary-storedValueForKey:]
*/
- (id)storedValueForKey: (NSString *)key
{
id value;
EOFLOGObjectFnStartCond(@"EOKVC");
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@",
// key);
value = [self objectForKey: key];
if (!value)
{
if ([key isEqualToString: @"allValues"])
{
value = [self allValues];
}
else if ([key isEqualToString: @"allKeys"])
{
value = [self allKeys];
}
else if ([key isEqualToString: @"count"])
{
value = [NSNumber numberWithUnsignedInt: [self count]];
}
}
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@ value: %p (class=%@)",
// key, value, [value class]);
EOFLOGObjectFnStopCond(@"EOKVC");
return value;
}
/**
* First checks whether the entire keyPath is contained as a key
* in the receiver before invoking super's implementation.
* (The special quoted key handling will probably be moved
* to a GSWDictionary subclass to be used by GSWDisplayGroup.)
*/
- (id)valueForKeyPath: (NSString*)keyPath
{
id value = nil;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPath=\"%@\"",
// keyPath);
if ([keyPath hasPrefix: @"'"] && strictWO == NO) //user defined composed key
{
NSMutableArray *keyPathArray = [[[[keyPath stringByDeletingPrefix: @"'"]
componentsSeparatedByString: @"."]
mutableCopy] autorelease];
NSMutableString *key = [NSMutableString string];
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray);
while ([keyPathArray count] > 0)
{
id tmpKey;
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray);
tmpKey = [keyPathArray objectAtIndex: 0];
//EOFLOGObjectLevelArgs(@"EOKVC", @"tmpKey=%@", tmpKey);
[keyPathArray removeObjectAtIndex: 0];
if ([key length] > 0)
[key appendString: @"."];
if ([tmpKey hasSuffix: @"'"])
{
tmpKey = [tmpKey stringByDeletingSuffix: @"'"];
[key appendString: tmpKey];
break;
}
else
[key appendString: tmpKey];
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key);
}
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key);
value = [self valueForKey: key];
//EOFLOGObjectLevelArgs(@"EOKVC",@"key=%@ tmpValue: %p (class=%@)",
// key,value,[value class]);
if (value && [keyPathArray count] > 0)
{
NSString *rightKeyPath = [keyPathArray
componentsJoinedByString: @"."];
//EOFLOGObjectLevelArgs(@"EOKVC", @"rightKeyPath=%@",
// rightKeyPath);
value = [value valueForKeyPath: rightKeyPath];
}
}
else
{
/*
* Return super valueForKeyPath: only
* if there's no object for entire key keyPath
*/
value = [self objectForKey: keyPath];
EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=%@ tmpValue: %p (class=%@)",
keyPath,value,[value class]);
if (!value)
value = [super valueForKeyPath: keyPath];
}
//EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=%@ value: %p (class=%@)",
// keyPath,value,[value class]);
EOFLOGObjectFnStopCond(@"EOKVC");
return value;
}
/**
* First checks whether the entire keyPath is contained as a key
* in the receiver before invoking super's implementation.
* (The special quoted key handling will probably be moved
* to a GSWDictionary subclass to be used by GSWDisplayGroup.)
*/
- (id)storedValueForKeyPath: (NSString*)keyPath
{
id value = nil;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
//EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=\"%@\"",
// keyPath);
if ([keyPath hasPrefix: @"'"] && strictWO == NO) //user defined composed key
{
NSMutableArray *keyPathArray = [[[[keyPath stringByDeletingPrefix: @"'"]
componentsSeparatedByString: @"."]
mutableCopy] autorelease];
NSMutableString *key = [NSMutableString string];
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray);
while ([keyPathArray count] > 0)
{
id tmpKey;
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray);
tmpKey = [keyPathArray objectAtIndex: 0];
//EOFLOGObjectLevelArgs(@"EOKVC", @"tmpKey=%@", tmpKey);
[keyPathArray removeObjectAtIndex: 0];
if ([key length] > 0)
[key appendString: @"."];
if ([tmpKey hasSuffix: @"'"])
{
tmpKey = [tmpKey stringByDeletingSuffix: @"'"];
[key appendString: tmpKey];
break;
}
else
[key appendString: tmpKey];
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key);
}
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key);
value = [self storedValueForKey: key];
//EOFLOGObjectLevelArgs(@"EOKVC",@"key=%@ tmpValue: %p (class=%@)",
// key,value,[value class]);
if (value && [keyPathArray count] > 0)
{
NSString *rightKeyPath = [keyPathArray
componentsJoinedByString: @"."];
EOFLOGObjectLevelArgs(@"EOKVC", @"rightKeyPath=%@",
rightKeyPath);
value = [value storedValueForKeyPath: rightKeyPath];
}
}
else
{
/*
* Return super valueForKeyPath: only
* if there's no object for entire key keyPath
*/
value = [self objectForKey: keyPath];
//EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=%@ tmpValue: %p (class=%@)",
// keyPath,value,[value class]);
if (!value)
value = [super storedValueForKeyPath: keyPath];
}
//EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=%@ value: %p (class=%@)",
// keyPath,value,[value class]);
EOFLOGObjectFnStopCond(@"EOKVC");
return value;
}
@end
@interface NSMutableDictionary(EOKeyValueCodingPrivate)
- (void)takeValue: (id)value
forKeyPath: (NSString *)keyPath
isSmart: (BOOL)smartFlag;
@end
@implementation NSMutableDictionary (EOKVCGNUstepExtensions)
/**
* Method to augment the NSKeyValueCoding implementation
* to account for added functionality such as quoted key paths.
* (The special quoted key handling will probably be moved
* to a GSWDictionary subclass to be used by GSWDisplayGroup.
* this method then becomes obsolete.)
*/
- (void)smartTakeValue: (id)value
forKeyPath: (NSString*)keyPath
{
[self takeValue:value
forKeyPath:keyPath
isSmart:YES];
}
/**
* Overrides gnustep-base and Foundations implementation
* to account for added functionality such as quoted key paths.
* (The special quoted key handling will probably be moved
* to a GSWDictionary subclass to be used by GSWDisplayGroup.
* this method then becomes obsolete.)
*/
- (void)takeValue: (id)value
forKeyPath: (NSString *)keyPath
{
[self takeValue:value
forKeyPath:keyPath
isSmart:NO];
}
/**
* Support method to augment the NSKeyValueCoding implementation
* to account for added functionality such as quoted key paths.
* (The special quoted key handling will probably be moved
* to a GSWDictionary subclass to be used by GSWDisplayGroup.
* this method then becomes obsolete.)
*/
- (void)takeValue: (id)value
forKeyPath: (NSString *)keyPath
isSmart: (BOOL)smartFlag
{
EOFLOGObjectFnStartCond(@"EOKVC");
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPath=\"%@\"",
// keyPath);
INITIALIZE;
if ([keyPath hasPrefix: @"'"] && strictWO == NO) //user defined composed key
{
NSMutableArray *keyPathArray = [[[[keyPath stringByDeletingPrefix: @"'"]
componentsSeparatedByString: @"."]
mutableCopy] autorelease];
NSMutableString *key = [NSMutableString string];
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray);
while ([keyPathArray count] > 0)
{
id tmpKey;
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray);
tmpKey = RETAIN([keyPathArray objectAtIndex: 0]);
//EOFLOGObjectLevelArgs(@"EOKVC", @"tmpKey=%@", tmpKey);
[keyPathArray removeObjectAtIndex: 0];
if ([key length] > 0)
[key appendString: @"."];
if ([tmpKey hasSuffix: @"'"])
{
ASSIGN(tmpKey, [tmpKey stringByDeletingSuffix: @"'"]);
[key appendString: tmpKey];
break;
}
else
[key appendString: tmpKey];
RELEASE(tmpKey);
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key);
}
//EOFLOGObjectLevelArgs(@"EOKVC",@"key=%@",key);
//EOFLOGObjectLevelArgs(@"EOKVC",@"left keyPathArray=\"%@\"",
// keyPathArray);
if ([keyPathArray count] > 0)
{
id obj = [self objectForKey: key];
if (obj)
{
NSString *rightKeyPath = [keyPathArray
componentsJoinedByString: @"."];
//EOFLOGObjectLevelArgs(@"EOKVC",@"rightKeyPath=\"%@\"",
// rightKeyPath);
if (smartFlag)
[obj smartTakeValue: value
forKeyPath: rightKeyPath];
else
[obj takeValue: value
forKeyPath: rightKeyPath];
}
}
else
{
if (value)
[self setObject: value
forKey: key];
else
[self removeObjectForKey: key];
}
}
else
{
if (value == nil)
{
[self removeObjectForKey: keyPath];
}
else
{
[self setObject: value forKey: keyPath];
}
}
EOFLOGObjectFnStopCond(@"EOKVC");
}
/**
* Calls [NSMutableDictionary-setObject:forKey:] using the full keyPath
* as a key, if the value is non nil. Otherwise calls
* [NSDictionary-removeObjectForKey:] with the full keyPath.
* (The special quoted key handling will probably be moved
* to a GSWDictionary subclass to be used by GSWDisplayGroup.)
*/
- (void)takeStoredValue: (id)value
forKeyPath: (NSString *)keyPath
{
EOFLOGObjectFnStartCond(@"EOKVC");
//EOFLOGObjectLevelArgs(@"EOKVC",@"keyPath=\"%@\"",
// keyPath);
if ([keyPath hasPrefix: @"'"]) //user defined composed key
{
NSMutableArray *keyPathArray = [[[[keyPath stringByDeletingPrefix: @"'"]
componentsSeparatedByString: @"."]
mutableCopy] autorelease];
NSMutableString *key = [NSMutableString string];
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray);
while ([keyPathArray count] > 0)
{
id tmpKey;
//EOFLOGObjectLevelArgs(@"EOKVC", @"keyPathArray=%@", keyPathArray);
tmpKey = [keyPathArray objectAtIndex: 0];
//EOFLOGObjectLevelArgs(@"EOKVC", @"tmpKey=%@", tmpKey);
[keyPathArray removeObjectAtIndex: 0];
if ([key length] > 0)
[key appendString: @"."];
if ([tmpKey hasSuffix: @"'"])
{
tmpKey = [tmpKey stringByDeletingSuffix: @"'"];
[key appendString: tmpKey];
break;
}
else
[key appendString: tmpKey];
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key);
}
//EOFLOGObjectLevelArgs(@"EOKVC", @"key=%@", key);
//EOFLOGObjectLevelArgs(@"EOKVC",@"left keyPathArray=\"%@\"",
// keyPathArray);
if ([keyPathArray count] > 0)
{
id obj = [self objectForKey: key];
if (obj)
{
NSString *rightKeyPath = [keyPathArray
componentsJoinedByString: @"."];
//EOFLOGObjectLevelArgs(@"EOKVC",@"rightKeyPath=\"%@\"",
// rightKeyPath);
[obj takeStoredValue: value
forKeyPath: rightKeyPath];
}
}
else
{
if (value)
[self setObject: value
forKey: key];
else
[self removeObjectForKey: key];
}
}
else
{
if (value)
[self setObject: value
forKey: keyPath];
else
[self removeObjectForKey: keyPath];
}
EOFLOGObjectFnStopCond(@"EOKVC");
}
@end
@implementation NSObject (EOKVCGNUstepExtensions)
/**
* This is a GDL2 extension. This convenience method iterates over
* the supplied keyPaths and determines the corresponding values by invoking
* valueForKeyPath: on the receiver. The results are returned an NSDictionary
* with the keyPaths as keys and the returned values as the dictionary's
* values. If valueForKeyPath: returns nil, it is replaced by the shared
* EONull instance.
*/
- (NSDictionary *)valuesForKeyPaths: (NSArray *)keyPaths
{
NSDictionary *values = nil;
int i;
int n;
NSMutableArray *newKeyPaths;
NSMutableArray *newVals;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
n = [keyPaths count];
newKeyPaths = AUTORELEASE([[NSMutableArray alloc] initWithCapacity: n]);
newVals = AUTORELEASE([[NSMutableArray alloc] initWithCapacity: n]);
for (i = 0; i < n; i++)
{
id keyPath = [keyPaths objectAtIndex: i];
id val = nil;
NS_DURING //DEBUG Only ?
{
val = [self valueForKeyPath: keyPath];
}
NS_HANDLER
{
NSLog(@"KVC:%@ EXCEPTION %@",
NSStringFromSelector(_cmd), localException);
NSDebugMLog(@"KVC:%@ EXCEPTION %@",
NSStringFromSelector(_cmd), localException);
[localException raise];
}
NS_ENDHANDLER;
if (val == nil)
{
val = null;
}
[newKeyPaths addObject: keyPath];
[newVals addObject: val];
}
values = [NSDictionary dictionaryWithObjects: newVals
forKeys: newKeyPaths];
EOFLOGObjectFnStopCond(@"EOKVC");
return values;
}
/**
* This is a GDL2 extension. This convenience method retrieves the object
* obtained by invoking valueForKey: on each path component until the one
* next to the last. It then invokes takeStoredValue:forKey: on that object
* with the last path component as the key.
*/
- (void)takeStoredValue: value
forKeyPath: (NSString *)key
{
NSArray *pathArray;
NSString *path;
id obj = self;
int i, count;
EOFLOGObjectFnStartCond(@"EOKVC");
pathArray = [key componentsSeparatedByString:@"."];
count = [pathArray count];
for (i = 0; i < (count - 1); i++)
{
path = [pathArray objectAtIndex: i];
obj = [obj valueForKey: path];
}
path = [pathArray lastObject];
[obj takeStoredValue: value forKey: path];
EOFLOGObjectFnStopCond(@"EOKVC");
}
/**
* This is a GDL2 extension. This convenience method retrieves the object
* obtained by invoking valueForKey: on each path component until the one
* next to the last. It then invokes storedValue:forKey: on that object
* with the last path component as the key, returning the result.
*/
- (id)storedValueForKeyPath: (NSString *)key
{
NSArray *pathArray = nil;
NSString *path;
id obj = self;
int i, count;
EOFLOGObjectFnStartCond(@"EOKVC");
pathArray = [key componentsSeparatedByString:@"."];
count = [pathArray count];
for(i=0; i < (count-1); i++)
{
path = [pathArray objectAtIndex:i];
obj = [obj valueForKey:path];
}
path = [pathArray lastObject];
obj=[obj storedValueForKey:path];
EOFLOGObjectFnStopCond(@"EOKVC");
return obj;
}
/**
* This is a GDL2 extension. This convenience method iterates over
* the supplied keyPaths and determines the corresponding values by invoking
* storedValueForKeyPath: on the receiver. The results are returned an
* NSDictionary with the keyPaths as keys and the returned values as the
* dictionary's values. If storedValueForKeyPath: returns nil, it is replaced
* by the shared EONull instance.
*/
- (NSDictionary *)storedValuesForKeyPaths: (NSArray *)keyPaths
{
NSDictionary *values = nil;
int i, n;
NSMutableArray *newKeyPaths = nil;
NSMutableArray *newVals = nil;
INITIALIZE;
EOFLOGObjectFnStartCond(@"EOKVC");
n = [keyPaths count];
newKeyPaths = [[[NSMutableArray alloc] initWithCapacity: n]
autorelease];
newVals = [[[NSMutableArray alloc] initWithCapacity: n]
autorelease];
for (i = 0; i < n; i++)
{
id keyPath = [keyPaths objectAtIndex: i];
id val = nil;
NS_DURING //DEBUG Only ?
{
val = [self storedValueForKeyPath: keyPath];
}
NS_HANDLER
{
NSLog(@"EXCEPTION %@", localException);
NSDebugMLog(@"EXCEPTION %@", localException);
[localException raise];
}
NS_ENDHANDLER;
if (val == nil)
val = null;
[newKeyPaths addObject: keyPath];
[newVals addObject: val];
}
values = [NSDictionary dictionaryWithObjects: newVals
forKeys: newKeyPaths];
EOFLOGObjectFnStopCond(@"EOKVC");
return values;
}
/**
* This is a GDL2 extension. Simply invokes takeValue:forKey:.
* This method provides a hook for EOGenericRecords KVC implementation,
* which takes relationship definitions into account.
*/
- (void)smartTakeValue: (id)anObject
forKey: (NSString *)aKey
{
[self takeValue: anObject
forKey: aKey];
}
/**
* This is a GDL2 extension. This convenience method invokes
* smartTakeValue:forKeyPath on the object returned by valueForKey: with
* the first path component.
* obtained by invoking valueForKey: on each path component until the one
* next to the last. It then invokes storedValue:forKey: on that object
* with the last path component as the key, returning the result.
*/
- (void)smartTakeValue: (id)anObject
forKeyPath: (NSString *)aKeyPath
{
NSRange r = [aKeyPath rangeOfString: @"."];
if (r.length == 0)
{
[self smartTakeValue: anObject
forKey: aKeyPath];
}
else
{
NSString *key = [aKeyPath substringToIndex: r.location];
NSString *path = [aKeyPath substringFromIndex: NSMaxRange(r) + 1];
[[self valueForKey: key] smartTakeValue: anObject
forKeyPath: path];
}
}
@end
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [RFA/gdl2] EOKeyValueCoding,
David Ayers <=