/** GSWAssociation.m - <title>GSWeb: Class GSWAssociation</title>

   Copyright (C) 1999-2004 Free Software Foundation, Inc.
   
   Written by:	Manuel Guesdon <mguesdon@orange-concept.com>
   Date:        Jan 1999
   
   $Revision: 37945 $
   $Date: 2014-06-11 20:21:08 +0200 (Wed, 11 Jun 2014) $

   This file is part of the GNUstep Web 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; if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   </license>
**/

#include "config.h"

#include "GSWeb.h"
#include "GSWPrivate.h"
#include "GSWKeyValueAssociation.h"
#include "GSWConstantValueAssociation.h"
#include "GSWBindingNameAssociation.h"
#include <math.h>
#include <GNUstepBase/NSObject+GNUstepBase.h>
#include <GNUstepBase/NSString+GNUstepBase.h>

#include <limits.h>
#include <float.h>

//#ifdef TCSDB
//#include <TCSimpleDB/TCSimpleDB.h>
//#endif
//#if HAVE_GDL2
//#include <EOControl/EOKeyValueCoding.h>
//#endif

static NSDictionary* localMinMaxDictionary=nil;
static NSMutableDictionary* associationsHandlerClasses=nil;
static NSLock* associationsLock=nil;
static NSMutableArray* associationsLogsHandlerClasses=nil;
static Class NSNumberClass = Nil;
static Class NSStringClass = Nil;

//====================================================================
@implementation GSWAssociation

+(void)initialize
{
  if (self==[GSWAssociation class])
    {
      associationsLock=[NSLock new];
      NSNumberClass = [NSNumber class];
      NSStringClass = [NSString class];

      if (!localMinMaxDictionary)
        {
          localMinMaxDictionary=[[NSDictionary dictionaryWithObjectsAndKeys:
                                                 [NSNumber numberWithShort:SCHAR_MIN],@"SCHAR_MIN",
                                               [NSNumber numberWithShort:SCHAR_MAX],@"SCHAR_MAX",
                                               [NSNumber numberWithShort:UCHAR_MAX],@"UCHAR_MAX",
                                               [NSNumber numberWithShort:CHAR_MIN],@"CHAR_MIN",
                                               [NSNumber numberWithShort:CHAR_MAX],@"CHAR_MAX",
                                               [NSNumber numberWithShort:SHRT_MIN],@"SHRT_MIN",
                                               [NSNumber numberWithShort:SHRT_MAX],@"SHRT_MAX",
                                               [NSNumber numberWithUnsignedInt:0],@"USHRT_MIN",
                                               [NSNumber numberWithUnsignedInt:USHRT_MAX],@"USHRT_MAX",
                                               [NSNumber numberWithInt:INT_MIN],@"INT_MIN",
                                               [NSNumber numberWithInt:INT_MAX],@"INT_MAX",
                                               [NSNumber numberWithUnsignedInt:0],@"UINT_MIN",
                                               [NSNumber numberWithUnsignedInt:UINT_MAX],@"UINT_MAX",
                                               [NSNumber numberWithLong:LONG_MIN],@"LONG_MIN",
                                               [NSNumber numberWithLong:LONG_MAX],@"LONG_MAX",
                                               [NSNumber numberWithUnsignedLong:0],@"ULONG_MIN",
                                               [NSNumber numberWithUnsignedLong:ULONG_MAX],@"ULONG_MAX",
#ifdef LONG_LONG_MAX                                               
                                               [NSNumber numberWithLongLong:LONG_LONG_MIN],@"LONG_LONG_MIN",
                                               [NSNumber numberWithLongLong:LONG_LONG_MAX],@"LONG_LONG_MAX",
#endif
#ifdef ULONG_LONG_MAX
                                               [NSNumber numberWithUnsignedLongLong:0],@"ULONG_LONG_MIN",
                                               [NSNumber numberWithUnsignedLongLong:ULONG_LONG_MAX],@"ULONG_LONG_MAX",
#endif
                                               [NSNumber numberWithFloat:FLT_MIN],@"FLOAT_MIN",
                                               [NSNumber numberWithFloat:FLT_MAX],@"FLOAT_MAX",
                                               [NSNumber numberWithFloat:DBL_MIN],@"DOUBLE_MIN",
                                               [NSNumber numberWithFloat:DBL_MAX],@"DOUBLE_MAX",
                                               nil,nil]
                                  retain];
        }
    }
}

//--------------------------------------------------------------------
+(void)dealloc
{
  DESTROY(localMinMaxDictionary);
  DESTROY(associationsHandlerClasses);
  DESTROY(associationsLogsHandlerClasses);
  DESTROY(associationsLock);
  
  [super dealloc];
}

//--------------------------------------------------------------------
-(void)dealloc
{
  DESTROY(_bindingName);
  DESTROY(_declarationName);
  DESTROY(_declarationType);
  [super dealloc];
}

//--------------------------------------------------------------------
// YES if we negate the result before returnig it.
-(BOOL)negate
{
  return _negate;
}

//--------------------------------------------------------------------
-(void) setNegate:(BOOL) yn
{
  _negate = yn;
}

//--------------------------------------------------------------------
- (BOOL)_hasBindingInParent:(GSWComponent*) parent
{
  return YES;
}

//--------------------------------------------------------------------
-(id)copyWithZone:(NSZone*)zone;
{
  GSWAssociation* clone = [[isa allocWithZone:zone] init];
  clone->_debugEnabled=_debugEnabled;
  [clone setDebugEnabledForBinding:_bindingName
         declarationName:_declarationName
         declarationType:_declarationType];
  return clone;
}

//--------------------------------------------------------------------
-(NSString*)description
{
  [self notImplemented: _cmd];	//TODOFN
  return [super description];
}

//--------------------------------------------------------------------
-(NSString*)bindingName
{
  return _bindingName;
}

//--------------------------------------------------------------------
-(NSString*)declarationName
{
  return _declarationName;
}

//--------------------------------------------------------------------
-(NSString*)declarationType
{
  return _declarationType;
}

//--------------------------------------------------------------------
//	isValueConstant

-(BOOL)isValueConstant 
{
  return YES;
}

//--------------------------------------------------------------------
//	isValueSettable

- (BOOL)isValueSettable 
{
  return NO;
}

//--------------------------------------------------------------------
- (BOOL) isValueSettableInComponent:(GSWComponent*) comp
{
  return [self isValueSettable];
}

//--------------------------------------------------------------------
- (BOOL) isValueConstantInComponent:(GSWComponent*) comp
{
  return [self isValueConstant];
}


//--------------------------------------------------------------------
//	setValue:inComponent:
-(void)setValue:(id)value
    inComponent:(GSWComponent*)component
{
  [self subclassResponsibility:_cmd];
}

- (void) _setValueNoValidation:(id) aValue inComponent:(GSWComponent*) component
{
  [self setValue:aValue inComponent:component];
}

//--------------------------------------------------------------------
//	valueInComponent:
-(id)valueInComponent:(GSWComponent*)component;
{
  return [self subclassResponsibility:_cmd];
}

//--------------------------------------------------------------------
// added in WO5?
// they call it booleanValueInComponent:
- (BOOL) boolValueInComponent:(GSWComponent*)component
{
  id value = [self valueInComponent: component];
  
  if (value==nil)
    {
      if (_negate)
	return YES;
      else
	return NO;
    }
  if ([value isKindOfClass: NSNumberClass])
    {
      if (_negate)
	return (![value boolValue]);
      else
	return [value boolValue];
    }
  else if ([value isKindOfClass: NSStringClass])
    {
      NSString* tmpStr = nil;
      int  length = [value length];
      if (length >= 2 && length <= 5)
	{
	  tmpStr = [value lowercaseString];
	  if ([tmpStr isEqual:@"no"]
	      || [tmpStr isEqual:@"false"]
	      || [tmpStr isEqual:@"nil"]
	      || [tmpStr isEqual:@"null"])
	    {
	      if (_negate)
		return YES;
	      else
		return NO;
	    }
	}
      if ([tmpStr isEqual:@"0"])
	{
	  if (_negate)
	    return YES;
	  else
	    return NO;
	}
      if (_negate)
	return NO;
      else           
	return YES;
    }
  if (_negate)
    return NO;
  else     
    return YES;
}


//--------------------------------------------------------------------
//	associationWithKeyPath:

+(GSWAssociation*)associationWithKeyPath:(NSString*)keyPath
{
  GSWAssociation  * newAssoc = nil;
  BOOL              doNegate = NO;
  NSString        * newPath  = keyPath;
  
  if (newPath) {
    doNegate = [newPath hasPrefix:@"!"];
    if (doNegate) {
      newPath = [newPath stringByDeletingPrefix:@"!"];
    }
    if ([newPath hasPrefix:@"^"] || (!WOStrictFlag && [newPath hasPrefix:@"~"])) {
      newAssoc = [[[GSWBindingNameAssociation alloc] initWithKeyPath: newPath] autorelease];
    } else {
      newAssoc = [[[GSWKeyValueAssociation alloc]initWithKeyPath: newPath] autorelease];
    }
    if (doNegate) {
      [newAssoc setNegate:YES];               // default is NO so we may safe a call here
    }
    return newAssoc;
  }

  return nil;
}


//--------------------------------------------------------------------
//	associationWithValue:

+(GSWAssociation*)associationWithValue:(id)value
{
  //OK
  return [[[GSWConstantValueAssociation alloc]initWithValue:value] autorelease];
}

//--------------------------------------------------------------------
//	associationFromString:
//NDFN
+(GSWAssociation*)associationFromString:(NSString*)string
{
  GSWAssociation* assoc=nil;

  if ([string length]<=0)
    assoc=[self associationWithValue:string];
  else
    {
      NSString* trimmedString=[string stringByTrimmingSpaces];
      if ([trimmedString isEqualToString:NSTYES])
        {
          assoc=[self associationWithValue:GSWNumberYes];
        }
      else if ([trimmedString isEqualToString:NSTNO])
        {
          assoc=[self associationWithValue:GSWNumberNo];
        }
      else if ([trimmedString hasPrefix:@"^"])
        {
          assoc=[self associationWithKeyPath:trimmedString];
        }
      else if ([trimmedString hasPrefix:@"\""])
        {
          if ([trimmedString hasSuffix:@"\""])
            {
              assoc=[self associationWithValue:[[trimmedString stringByDeletingPrefix:@"\""] stringByDeletingSuffix:@"\""]];
            }
          else
            {
              ExceptionRaise(@"GSWAssociation",@"String '%@' start with a \" but doesn't finish with a \"",
                             trimmedString);
            }
        }
      else if ([trimmedString hasPrefix:@"\'"])
        {
          if ([trimmedString hasSuffix:@"\'"])
            {
              assoc=[self associationWithValue:[[trimmedString stringByDeletingPrefix:@"\'"] stringByDeletingSuffix:@"\'"]];
            }
          else
            {
              ExceptionRaise(@"GSWAssociation",@"String '%@' starts with a \"'\" but does not end with a \"'\"",
                             trimmedString);
            }
        }
      else if ([trimmedString hasPrefix:@"#"])
        {
          NSString* numberString=[trimmedString stringByDeletingPrefix:@"#"];
          //char* cString=[numberString lossyCString];//TODO
          const char* cString=[numberString UTF8String];
          char* endPtr=NULL;
          int value=strtol(cString,&endPtr,16);
          if (endPtr && *endPtr)
            {
              ExceptionRaise(@"GSWAssociation",@"String '%@' start with a '#' but doesn't countain an hexadecimal number (on %dth Character)",
                             trimmedString,
                             (int)(endPtr-cString+1));
            }
          assoc=[self associationWithValue:GSWIntNumber(value)];
        }
      else
        {
          NSNumber* limit=[localMinMaxDictionary objectForKey:trimmedString];
          if (limit)
            {
              assoc=[self associationWithValue:limit];
            }
          else
            {
              NSCharacterSet* cset=[NSCharacterSet characterSetWithCharactersInString:@"-+0123456789"];
              NSRange firstCharRange=[trimmedString rangeOfCharacterFromSet:cset
                                                    options:0
                                                    range:NSMakeRange(0,1)];

              if (firstCharRange.length==0 || firstCharRange.location!=0)
                {
                  assoc=[self associationWithKeyPath:trimmedString];
                }
              else
                {
                  //char* cString=[trimmedString lossyCString];//TODO
                  const char* cString=[trimmedString UTF8String];
                  char* endPtr=NULL;
                  int value=strtol(cString,&endPtr,10);

                  if (endPtr && *endPtr)
                    {
                      ExceptionRaise(@"GSWAssociation",
                                     @"String '%@' must be a good number",
                                     trimmedString);
                    }
                  assoc=[self associationWithValue:GSWIntNumber(value)];
                }
            }
        }
    }
  return assoc;
}

//--------------------------------------------------------------------
+(void)setClasse:(Class)class
      forHandler:(NSString*)handler
{
  LoggedLockBeforeDate(associationsLock,GSW_LOCK_LIMIT);
  if (!associationsHandlerClasses)
    {
      if (class)
        associationsHandlerClasses=[NSMutableDictionary new];
    }
  if (class)
    [associationsHandlerClasses setObject:class
                                forKey:handler];
  else if (associationsHandlerClasses)
    [associationsHandlerClasses removeObjectForKey:handler];
  LoggedUnlock(associationsLock);
}

//--------------------------------------------------------------------
+(void)addLogHandlerClasse:(Class)class
{
  LoggedLockBeforeDate(associationsLock,GSW_LOCK_LIMIT);
  if (!associationsLogsHandlerClasses)
    {
      if (class)
        associationsLogsHandlerClasses=[NSMutableArray new];
    }
  if (class)
    [associationsLogsHandlerClasses addObject:class];
  LoggedUnlock(associationsLock);
}

//--------------------------------------------------------------------
+(void)removeLogHandlerClasse:(Class)class
{
  LoggedLockBeforeDate(associationsLock,GSW_LOCK_LIMIT);
  if (associationsHandlerClasses)
    {
      if (class)
        [associationsLogsHandlerClasses removeObject:class];
    }
  LoggedUnlock(associationsLock);
}

//--------------------------------------------------------------------
// returns the binding String as in the wod.
// override in subclasses
- (NSString*) bindingInComponent:(GSWComponent*) component
{
  return nil;
}


//--------------------------------------------------------------------
-(BOOL)isImplementedForComponent:(NSObject*)component
{
  return YES;
}

//--------------------------------------------------------------------
-(NSString*)keyPath
{
  [self subclassResponsibility:_cmd];
  return nil;
}

//--------------------------------------------------------------------
-(void)logValue:(id)value
         forSet:(BOOL)set
{
  if (_debugEnabled)
    {
      if (associationsLogsHandlerClasses)
        {
          LoggedLockBeforeDate(associationsLock,GSW_LOCK_LIMIT);
          NS_DURING
            {
              int i=0;
              Class class=Nil;
              int handlerCount=[associationsLogsHandlerClasses count];
              NSString* debugDescription=[self debugDescription];
              for(i=0;i<handlerCount;i++)
                {
                  class=[associationsLogsHandlerClasses objectAtIndex:i];
                  if (set)
                    [class logSetValueForDeclarationNamed:_declarationName
                           type:_declarationType
                           bindingNamed:_bindingName
                           associationDescription:debugDescription
                           value:value];
                  else
                    [class logTakeValueForDeclarationNamed:_declarationName
                           type:_declarationType
                           bindingNamed:_bindingName
                           associationDescription:debugDescription
                           value:value];
                }
            }
          NS_HANDLER
            {
              LoggedUnlock(associationsLock);
              [localException raise];
            }
          NS_ENDHANDLER;
          LoggedUnlock(associationsLock);
        }
    }
}

//--------------------------------------------------------------------
-(void)_logPullValue:(id)value
	 inComponent:(GSWComponent*) component
{
  [GSWApp logTakeValueForDeclarationNamed:_declarationName
	  type:_declarationType
	  bindingNamed:_bindingName
	  associationDescription:[self debugDescription]
	  value:value];
}

//--------------------------------------------------------------------
-(void)_logPushValue:(id)value
	 inComponent:(GSWComponent*) component
{
  [GSWApp logSetValueForDeclarationNamed:_declarationName
	  type:_declarationType
	  bindingNamed:_bindingName
	  associationDescription:[self debugDescription]
	  value:value];
}

//--------------------------------------------------------------------
-(void)logTakeValue:(id)value
{
  [self logValue:value
        forSet:NO];
}

//--------------------------------------------------------------------
-(void)logSetValue:(id)value
{
  [self logValue:value
        forSet:YES];
}

//--------------------------------------------------------------------
-(void)logSynchronizeForValue:(id)value
                  inComponent:(GSWComponent*)component
            componentToParent:(BOOL)componentToParent
{
  if (associationsHandlerClasses)
    {
      LoggedLockBeforeDate(associationsLock,GSW_LOCK_LIMIT);
      NS_DURING
        {
          int i=0;
          Class class=Nil;
          int handlerCount=[associationsLogsHandlerClasses count];
          for(i=0;i<handlerCount;i++)
            {
              class=[associationsLogsHandlerClasses objectAtIndex:i];
              if (componentToParent)
                [class	logSynchronizeComponentToParentForValue:value
                        association:self
                        inComponent:component];
              else
                [class logSynchronizeParentToComponentForValue:value
                       association:self
                       inComponent:component];
            }
        }
      NS_HANDLER
        {
          LoggedUnlock(associationsLock);
          [localException raise];
        }
      NS_ENDHANDLER;
      LoggedUnlock(associationsLock);
    }
}

//--------------------------------------------------------------------
-(void)logSynchronizeComponentToParentForValue:(id)value
                                   inComponent:(GSWComponent*)component
{
  [self logSynchronizeForValue:value
        inComponent:component
        componentToParent:YES];
}

//--------------------------------------------------------------------
-(void)logSynchronizeParentToComponentForValue:(id)value
                                   inComponent:(GSWComponent*)component
{
  [self logSynchronizeForValue:value
        inComponent:component
        componentToParent:NO];
}

//--------------------------------------------------------------------
-(NSString*)debugDescription
{
  return NSStringFromClass([self class]);
}

//--------------------------------------------------------------------

-(void)setDebugEnabledForBinding:(NSString*)bindingName
                 declarationName:(NSString*)declarationName
                 declarationType:(NSString*)declarationType
{
  _debugEnabled=YES;
  ASSIGN(_bindingName,bindingName);
  ASSIGN(_declarationName,declarationName);
  ASSIGN(_declarationType,declarationType);
}

//--------------------------------------------------------------------
+(id)valueInComponent:(GSWComponent*)object
           forKeyPath:(NSString*)keyPath
{
  static id EONullNull=nil;
  //TODO MultiThread Protection ?
  if (!EONullNull)
    EONullNull=[NSNull null];
  id retValue=nil;
  
  if (keyPath && object && object!=EONullNull)
    {
      NS_DURING
	{
	  retValue=[object valueForKeyPath:keyPath];
	}
      NS_HANDLER
	{
	  NSLog(@"Attempt to get %@ -%@ raised an exception (%@)",
		[object class],
		keyPath,
		localException);
	  localException = [localException exceptionByAddingToUserInfoKey:@"Invalid Ivars/Methods"
					   format:@"-[%@ %@]",
					   [object class],
					   keyPath];
	  
	  [localException raise];
	}
      NS_ENDHANDLER;
      if (retValue==EONullNull)
	retValue=nil;
    }
  
  return retValue;
}

//--------------------------------------------------------------------

+(void)setValue:(id)value
    inComponent:(GSWComponent*)object
     forKeyPath:(NSString*)keyPath
{
  id tmpObject = nil;
  NSString *tmpKey = nil;
  
  if (keyPath)
    {
      NSRange       r = [keyPath rangeOfString: @"."];
    
      if (r.length == 0)
	{
	  tmpObject = object;
	  tmpKey = keyPath;
	}
      else
	{
	  NSString  *key = [keyPath substringToIndex: r.location];
	  tmpObject = [object valueForKey: key];
	  tmpKey = [keyPath substringFromIndex: NSMaxRange(r)];
	}
      if (tmpObject) //&& [object isKindOfClass:[GSWComponent class]]
	{
	  NSError * outError = nil;
	  
	  BOOL ok = [tmpObject validateValue:&value
			       forKey:tmpKey
			       error:&outError];
	  if (ok == NO)
	    {
	      NSException  * exception=nil;
	      NSDictionary * uInfo;
	      NSString     * errorStr = @"unknown reason";
	      
	      uInfo = [NSDictionary dictionaryWithObjectsAndKeys:
				      tmpObject, @"EOValidatedObjectUserInfoKey", //Target Object
				    keyPath, @"EOValidatedPropertyUserInfoKey",   //Target Object Key Path
				    nil];
	      
	      if (outError
		  && [outError userInfo])
		{
		  errorStr = [[outError userInfo] valueForKey:NSLocalizedDescriptionKey];
		}
	      
	      exception=[NSException exceptionWithName:@"EOValidationException"
				     reason:errorStr
				     userInfo:uInfo];
	      
	      [object validationFailedWithException:exception
		      value:value
		      keyPath:keyPath];
	    }
	  else
	    {
	      // all fine, set the value
	      [tmpObject setValue:value
			 forKey:tmpKey];
	    }
	}
    }
}


@end

//===================================================================================
@implementation NSDictionary (GSWAssociation)

-(BOOL)isAssociationDebugEnabledInComponent:(GSWComponent*)component
{
  BOOL debug=NO;
  GSWAssociation* debugAssociation=[self objectForKey:@"GSWDebug"];
  if (debugAssociation)
    {
      id value=[debugAssociation valueInComponent:component];
      debug=boolValueWithDefaultFor(value,NO);
    }
  return debug;
}
  
-(void)associationsSetDebugEnabled
{
  NSEnumerator* enumerator=nil;
  id key=nil;
  id association=nil;
  enumerator = [self keyEnumerator];
  while ((key = [enumerator nextObject]))
    {
      association=[self objectForKey:key];
      [association setDebugEnabledForBinding:@""
                   declarationName:key
                   declarationType:@""];	//TODO
    }
}

-(void)associationsSetValuesFromObject:(id)from
                              inObject:(id)to
{
  NSEnumerator *enumerator = nil;
  id key=nil;
  id varValue=nil;
  id var=nil;

  enumerator = [self keyEnumerator];
  while ((key = [enumerator nextObject]))
    {
      var=[self objectForKey:key];
      if ([var isKindOfClass:[GSWAssociation class]])
        varValue=[var valueInComponent:from];
      else
        varValue=var;

      if (![key isKindOfClass:[GSWAssociation class]])
        key=[GSWAssociation associationWithKeyPath:key];
      [key setValue:varValue
            inComponent:to];
    }
}

//--------------------------------------------------------------------
-(NSDictionary*)associationsWithoutPrefix:(NSString*)prefix
                               removeFrom:(NSMutableDictionary*)removeFrom
{
  NSMutableDictionary* newAssociation=nil;
  NSEnumerator *enumerator = nil;
  id key=nil;
  id varKey=nil;
  id varKeyAssociation=nil;
  id value=nil;

  newAssociation=(NSMutableDictionary*)[NSMutableDictionary dictionary];
  enumerator = [self keyEnumerator];
  while ((key = [enumerator nextObject]))
    {
      if ([key hasPrefix:prefix])
        {
          value=[self objectForKey:key];
          varKey=[key stringByDeletingPrefix:prefix];
          varKeyAssociation=[GSWAssociation associationWithKeyPath:varKey];
          [newAssociation setObject:value
                          forKey:varKeyAssociation];
          [removeFrom removeObjectForKey:key];
        }
    }
  newAssociation=[NSDictionary dictionaryWithDictionary:newAssociation];

  return newAssociation;
}

//--------------------------------------------------------------------
-(NSDictionary*)dictionaryByReplacingStringsWithAssociations
{
  NSMutableDictionary* newDictionary=[NSMutableDictionary dictionary];
  NSEnumerator* enumerator=[self keyEnumerator];
  id key=nil;
  id value=nil;
  id newValue=nil;
  while ((key=[enumerator nextObject]))
    {
      value=[self objectForKey:key];
      if ([value isKindOfClass:[NSString class]])
        {
          newValue=[GSWAssociation associationFromString:value];
          NSAssert(newValue,@"Nil value");
        }
      else if ([value isKindOfClass:[NSArray class]])
        {
          newValue=[value arrayByReplacingStringsWithAssociations];
          NSAssert(newValue,@"Nil value");
        }
      else if ([value isKindOfClass:[NSDictionary class]])
        {
          newValue=[value dictionaryByReplacingStringsWithAssociations];
          NSAssert(newValue,@"Nil value");
        }
      else
        newValue=value;
      [newDictionary setObject:newValue
                     forKey:key];
    }
  return [NSDictionary dictionaryWithDictionary:newDictionary];
}

@end

//===================================================================================
@implementation NSArray (GSWAssociation)
-(NSArray*)arrayByReplacingStringsWithAssociations
{
  NSMutableArray* newArray=[NSMutableArray array];
  int count=[self count];
  int i=0;
  id value=nil;
  id newValue=nil;
  IMP oaiIMP=NULL;
  for(i=0;i<count;i++)
    {
      value=GSWeb_objectAtIndexWithImpPtr(self,&oaiIMP,i);
      if ([value isKindOfClass:[NSString class]])
        {
          newValue=[GSWAssociation associationFromString:value];
        }
      else if ([value isKindOfClass:[NSArray class]])
        {
          newValue=[value arrayByReplacingStringsWithAssociations];
        }
      else if ([value isKindOfClass:[NSDictionary class]])
        {
          newValue=[value dictionaryByReplacingStringsWithAssociations];
        }
      else
        newValue=value;
      [newArray addObject:newValue];
    }
  return [NSArray arrayWithArray:newArray];
}
@end
