bug-gnustep
[Top][All Lists]
Advanced

[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

reply via email to

[Prev in Thread] Current Thread [Next in Thread]