/** 
   EOModel.m <title>EOModel Class</title>

   Copyright (C) 2000-2002,2003,2004,2005 Free Software Foundation, Inc.

   Author: Mirko Viviani <mirko.viviani@gmail.com>
           Manuel Guesdon <mguesdon@orange-concept.com>
   Date: February 2000

   $Revision: 37928 $
   $Date: 2014-05-30 19:37:46 +0200 (Fri, 30 May 2014) $

   <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 3 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,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
   </license>
**/

#include "config.h"

#ifdef GNUSTEP
#include <Foundation/NSBundle.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSObjCRuntime.h>
#include <Foundation/NSException.h>
#include <Foundation/NSFileManager.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSNotification.h>
#include <Foundation/NSPathUtilities.h>
#include <Foundation/NSDebug.h>
#else
#include <Foundation/Foundation.h>
#include <GNUstepBase/GNUstep.h>
#include <GNUstepBase/NSDebug+GNUstepBase.h>
#include <GNUstepBase/NSObject+GNUstepBase.h>
#endif


#include <GNUstepBase/GSObjCRuntime.h>

#include <EOControl/EOGenericRecord.h>
#include <EOControl/EOFault.h>
#include <EOControl/EOKeyGlobalID.h>
#include <EOControl/EOClassDescription.h>
#include <EOControl/EOObserver.h>
#include <EOControl/EOKeyValueCoding.h>
#include <EOControl/EONSAddOns.h>
#include <EOControl/EODebug.h>

#include <EOAccess/EOModel.h>
#include <EOAccess/EOEntity.h>
#include <EOAccess/EOStoredProcedure.h>
#include <EOAccess/EOModelGroup.h>
#include <EOAccess/EOAccessFault.h>
#include <EOAccess/EOAdaptor.h>
#include <EOAccess/EOAttribute.h>
#include <EOAccess/EORelationship.h>

#include "EOEntityPriv.h"
#include "EOPrivate.h"
#include "EOAttributePriv.h"

#define DEFAULT_MODEL_VERSION 3

NSString *EOEntityLoadedNotification = @"EOEntityLoadedNotification";

@interface EOModel (EOModelPrivate)

+ (NSString *) _formatModelPath: (NSString *)path checkFileSystem: (BOOL)chkFS;

- (EOEntity *) _verifyBuiltEntityObject: (id)entity
                                  named: (NSString *)name;

@end /* EOModel (EOModelPrivate) */

@implementation EOModel

+ (void)initialize
{
  static BOOL initialized=NO;
  if (!initialized)
    {
      initialized=YES;
    };
};

+ (EOModel*) model
{
  return AUTORELEASE([[self alloc] init]);
}

+ (NSString*) findPathForModelNamed: (NSString *)modelName
{
  NSString *modelPath = nil;
  NSString *tmpModelName = nil;
  NSString *tmpPath = nil;
  NSBundle *bundle = nil;
  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, NSAllDomainsMask, YES);

  tmpModelName = [modelName lastPathComponent];
  EOFLOGClassLevelArgs(@"gsdb", @"modelName=%@ tmpModelName=%@",
		       modelName, tmpModelName);

  tmpPath = [[modelName stringByStandardizingPath]
              stringByDeletingLastPathComponent]; 
  EOFLOGClassLevelArgs(@"gsdb", @"modelName=%@ tmpPath=%@",
			modelName, tmpPath);

  bundle = [NSBundle mainBundle];
  modelPath = [bundle pathForResource: modelName
                      ofType: @"eomodel"];

  EOFLOGClassLevelArgs(@"gsdb", @"modelName=%@ modelPath=%@",
			modelName, modelPath);

  if (!modelPath)
    {
      modelPath = [bundle pathForResource: modelName
                          ofType: @"eomodeld"];

      EOFLOGClassLevelArgs(@"gsdb", @"modelName=%@ modelPath=%@",
			    modelName, modelPath);

      if (!modelPath)
        {
          if ([tmpPath length] == 0)
            {
              tmpPath = @"./";
              tmpPath = [tmpPath stringByStandardizingPath];
            }

          if ([[tmpModelName pathExtension] length] != 0)
            tmpModelName = [tmpModelName stringByDeletingPathExtension]; 
          
          EOFLOGClassLevelArgs(@"gsdb",
				@"modelName=%@ tmpPath=%@ tmpModelName=%@",
				modelName, tmpPath, tmpModelName);

          bundle = [NSBundle bundleWithPath: tmpPath];
          
          modelPath = [bundle pathForResource: tmpModelName
                              ofType: @"eomodel"];

          EOFLOGClassLevelArgs(@"gsdb", @"modelName=%@ modelPath=%@",
				modelName, modelPath);

          if (!modelPath)
            {          
              modelPath = [bundle pathForResource: tmpModelName
                                  ofType: @"eomodeld"];
              EOFLOGClassLevelArgs(@"gsdb", @"modelName=%@ modelPath=%@",
				    modelName, modelPath);

              if (!modelPath)
                {
                  int i, pathCount = [paths count];

                  for (i = 0; !modelPath && pathCount < i; i++)
                    {
                      EOFLOGClassLevelArgs(@"gsdb", @"Trying path:%@",
					    [paths objectAtIndex:i]);

                      bundle = [NSBundle bundleWithPath: [paths objectAtIndex:i]];
                      
                      modelPath = [bundle pathForResource: modelName
                                          ofType: @"eomodel"];

                      EOFLOGClassLevelArgs(@"gsdb",
					    @"modelName=%@ modelPath=%@",
					    modelName, modelPath);

                      if (!modelPath)
                        {
                          modelPath = [bundle pathForResource: modelName
                                              ofType: @"eomodeld"];

                          EOFLOGClassLevelArgs(@"gsdb",
						@"modelName=%@ modelPath=%@",
						modelName, modelPath);
                        }
                    }
                }
            }
        }
    }

   return modelPath;
}

- (id)init
{
  if ((self = [super init]))
    {
      // Turbocat
      _version = DEFAULT_MODEL_VERSION;
      
      _entitiesByName = [NSMutableDictionary new];
      _entitiesByClass = NSCreateMapTableWithZone(NSObjectMapKeyCallBacks, 
						  NSObjectMapValueCallBacks,
						  8,
						  [self zone]);
      _storedProcedures = [NSMutableArray new];
  
      [[NSNotificationCenter defaultCenter]
        addObserver: self
        selector: @selector(_classDescriptionNeeded:)
        name: EOClassDescriptionNeededNotification
        object: nil];

      [[NSNotificationCenter defaultCenter]
        addObserver: self
        selector: @selector(_classDescriptionNeeded:)
        name: EOClassDescriptionNeededForClassNotification
        object: nil];

      [[NSNotificationCenter defaultCenter]
        addObserver: self
        selector: @selector(_classDescriptionNeeded:)
        name: EOClassDescriptionNeededForEntityNameNotification
        object: nil];

      [EOClassDescription invalidateClassDescriptionCache];
    }



  return self;
}

- (void) dealloc
{
  [[NSNotificationCenter defaultCenter] removeObserver: self];
  [[self entities] makeObjectsPerformSelector:@selector(_setModel:)
	  			withObject:nil];
  if (_entitiesByClass)
    {
      NSFreeMapTable(_entitiesByClass);
      _entitiesByClass = NULL;
    }
  DESTROY(_storedProcedures);
  DESTROY(_entitiesByName);
  DESTROY(_entities);
  DESTROY(_name);
  DESTROY(_path);
  DESTROY(_adaptorName);
  DESTROY(_connectionDictionary);
  DESTROY(_userInfo);
  DESTROY(_internalInfo);
  DESTROY(_docComment);

  [super dealloc];
}

- (NSString*) path
{
  return _path;
}

- (NSString*) name
{
  return _name;
}

- (NSString*) adaptorName
{
  return _adaptorName;
}

- (NSString*) adaptorClassName
{
  return _adaptorClassName;
}

- (EOEntity*) entityNamed: (NSString *)name
{
  EOEntity *entity = nil;

  NSAssert(name,@"No entity name");

  entity = [_entitiesByName objectForKey: name];
  if (entity != nil)
    {
      entity = [self _verifyBuiltEntityObject: entity
					named: name];
    }

  return entity;
}

- (NSArray*) entities
{
  if (!_entities)
  {
    NSArray *entityNames = [self entityNames];
    
    // we need an mutable array here, otherwise we cannot remove entities from it. -- dw
    ASSIGN(_entities,[NSMutableArray arrayWithArray:[self resultsOfPerformingSelector: @selector(entityNamed:)
                       withEachObjectInArray: entityNames]]);
  }
  
  return _entities;
}

- (NSArray*) entityNames
{
  return [[_entitiesByName allKeys]
	   sortedArrayUsingSelector: @selector(compare:)];
}

- (NSArray *)storedProcedureNames
{
  /* We do not load procedures lazy 
     so we can rely on the instance variable.  */
  return [_storedProcedures valueForKey: @"name"];
}

- (EOStoredProcedure*)storedProcedureNamed: (NSString *)name
{
  EOStoredProcedure *proc;
  NSString *procName;
  unsigned i,n;

  n = [_storedProcedures count];
  for (i=0; i<n; i++)
    {
      proc = [_storedProcedures objectAtIndex: i];
      procName = [proc name];
      if ([procName isEqual: name])
	{
	  return proc;
	}
    }

  return nil;
}

- (NSArray*)storedProcedures
{
  /* We do not load procedures lazy 
     so we can rely on the instance variable.  */
  return _storedProcedures;
}

- (EOEntity*) entityForObject: (id)object
{
  EOEntity *entity = nil;
  NSString *entityName = nil;

  if ([EOFault isFault: object])
    {
      EOFaultHandler *handler = [EOFault handlerForFault: object];

      if ([handler respondsToSelector: @selector(globalID)] == YES)
        entityName = [[(EOAccessFaultHandler *)handler globalID]
		       entityName];
    }
  else
    {
      //  if ([object isKindOfClass:[EOGenericRecord class]])
      //    return [object entity];      
      entityName = [object entityName];
    }

  if (entityName)
    entity = [self entityNamed:entityName];

  return entity;
}

- (NSDictionary*) connectionDictionary
{
  return _connectionDictionary;
}

- (NSDictionary*) userInfo
{
  return _userInfo;
}

- (NSString*) description
{
  NSMutableDictionary *descdict;
  id obj;

  descdict = [NSMutableDictionary dictionaryWithCapacity: 6];
  obj = [self name];
  if (obj) [descdict setObject: obj forKey: @"name"];
  obj = [self adaptorName];
  if (obj) [descdict setObject: obj forKey: @"adaptorName"];
  obj = [self connectionDictionary];
  if (obj) [descdict setObject: obj forKey: @"connectionDictionary"];
  obj = [self userInfo];
  if (obj) [descdict setObject: obj forKey: @"userInfo"];
  obj = [self entities];
  if (obj) [descdict setObject: obj forKey: @"entities"];
  obj = [self storedProcedures];
  if (obj) [descdict setObject: obj forKey: @"storedProcedures"];

  obj = [descdict description];
  return obj;
}

- (NSString*) docComment
{
  return _docComment;
}

- (EOModelGroup*) modelGroup
{
  return _group;
}

- (EOAttribute *)prototypeAttributeNamed: (NSString *)attributeName
{
  NSString *entityName;
  EOEntity *entity;
  NSArray *attributes;
  EOAttribute *attribute = nil;
  int i, count;


  EOFLOGObjectLevelArgs(@"gsdb", @"attrName=%@", attributeName);

  entityName = [NSString stringWithFormat: @"EO%@Prototypes", _adaptorName];

  EOFLOGObjectLevelArgs(@"gsdb", @"entityName=%@", entityName);

  entity = [self entityNamed: entityName];

  if (!entity)
    entity = [_group entityNamed: entityName];

  if (!entity)
    entity = [_group entityNamed: @"EOPrototypes"];

  if (!entity && _adaptorName && [_adaptorName length] > 0)
    {
      EOAdaptor *adaptor;

      adaptor = [EOAdaptor adaptorWithName: _adaptorName];
      attributes = [adaptor prototypeAttributes];
    }
  else
    attributes = [entity attributes];

  EOFLOGObjectLevelArgs(@"gsdb", @"entity=%@ - attributes=%@",
			entity, attributes);

  if (attributes)
    {
      count = [attributes count];

      for (i = 0; i < count; i++)
	{
	  attribute = [attributes objectAtIndex: i];

	  if ([[attribute name]
		isEqualToString: attributeName])
	    break;
	}
    }

  EOFLOGObjectLevelArgs(@"gsdb", @"attribute=%@", attribute);


  return attribute;
}

@end


@implementation EOModel (EOModelFileAccess)

+ (EOModel*) modelWithContentsOfFile: (NSString *)path
{
  return AUTORELEASE([[self alloc] initWithContentsOfFile: path]);
}

- (id) initWithContentsOfFile: (NSString *)path
{
  NS_DURING
    {
      NSString *name = nil;
      NSString *modelPath = nil;
      NSString *indexPath = nil;
      NSString *fileContents = nil;
      NSDictionary *propList = nil;

      path = [path stringByStandardizingPath];
      modelPath = [[self class] _formatModelPath: path checkFileSystem: YES];
      NSAssert1(modelPath!=nil, @"Model does not exist at path %@",
                path );
      name = [[modelPath lastPathComponent] stringByDeletingPathExtension];
      [self setName: name];

      if ([[modelPath pathExtension] isEqualToString: @"eomodeld"])
        {
          indexPath =
              [modelPath stringByAppendingPathComponent: @"index.eomodeld"];
        }
      else
        {
          indexPath = modelPath;
        }

      fileContents = [NSString stringWithContentsOfFile: indexPath];
      NSAssert1(fileContents!=nil, @"File %@ could not be read.", indexPath);
      propList = [fileContents propertyList];
      EOFLOGObjectLevelArgs(@"gsdb", @"propList=%@", propList);
      NSAssert1(propList!=nil, @"Model at path %@ is invalid", indexPath);
      if([[indexPath pathExtension] isEqualToString: @"eomodeld"])
	{
	  NSString *connectionPath = [modelPath stringByAppendingPathComponent: @"connectionSettings.plist"];
	  NSString *externalConnectionSettings = [NSString stringWithContentsOfFile: connectionPath];
	  if (externalConnectionSettings!=nil)
	    {
	      id connectionProperties = [externalConnectionSettings propertyList];
	      //copy the original settings and merge the connection settings in
	      NSMutableDictionary *mutableProperties = [propList mutableCopy];
	      [mutableProperties setObject: connectionProperties
				    forKey: @"connectionDictionary"];
	      propList = mutableProperties;
	      [mutableProperties autorelease];
	    }
	}
      self = [self initWithTableOfContentsPropertyList: propList
                   path: modelPath];
      NSAssert2(self!=nil,@"Failed to initialize with path %@ and plist %@",
                modelPath,
                propList);
    }
  NS_HANDLER
    {
      NSLog(@"exception in EOModel initWithContentsOfFile:");
      NSLog(@"exception=%@", localException);
      [localException raise];
    }
  NS_ENDHANDLER;

  return self;
}

- (BOOL) _writePlist:(NSDictionary*)plist toFile:(NSString*) path
{
  NSData   * fileData;
  NSString * errorDescription = nil;
  NSError  * error = nil;
  BOOL       ok = NO;
  
  // we should use 
  // NSPropertyListSerialization dataWithPropertyList:format:options:error:
  // but that is not hacked to work on apple in Additions
  fileData = [NSPropertyListSerialization dataFromPropertyList:plist
                                                        format:NSPropertyListOpenStepFormat 
                                              errorDescription:&errorDescription];


  if (!fileData) {
    NSLog(@"%s: Cannot convert plist to data.", __PRETTY_FUNCTION__);
    return NO;
  }
  
  ok = [fileData writeToFile:path
                     options:NSAtomicWrite
                       error:&error];
  
  if (!ok) {
    NSLog(@"%s:%@", __PRETTY_FUNCTION__, error);
  }
  return ok;
}

/**
 * Deletes any non-known files/paths at path.
 * private.
 */

- (void) _deleteTrashAtPath:(NSString*) path
{
  NSFileManager         * manager = [NSFileManager defaultManager];
  NSDirectoryEnumerator * dirEnumer = [manager enumeratorAtPath:path];

  NSArray               * knownTrashPaths = [NSArray arrayWithObjects:@".svn",
                                             @".CVS", @".git",
                                             nil];
  NSString              * fileName = nil;
  
  while ((fileName = [dirEnumer nextObject])) {
    NSString * prefix = [[fileName pathComponents] objectAtIndex:0];
    if (([knownTrashPaths containsObject:prefix] == NO)) {
      NSString * fullPath = nil;

      fullPath = [path stringByAppendingPathComponent:fileName];
      if ([manager removeFileAtPath: fullPath handler: nil] == NO)
	    {
	      [NSException raise: NSInvalidArgumentException
                    format: @"Could not remove %@", fullPath];
	    }
    }
  }
}

/**
 * <p>Writes the receivers plist representation into an
 * .eomodeld file wrapper located at path.  </p>
 * <p>Depending on the the path extension .eomodeld or .eomodel
 * the corresponding format will be used.
 * If the path has neither .eomodeld nor .eomodel path
 * extension, .eomodeld will be used.</p>
 * <p>If the file located at path already exists, a back is created
 * by appending a '~' character to file name.
 * If a backup file already exists, when trying to create a backup,
 * the old backup will be deleted.</p>
 * <p>If any of the file operations fail, an NSInvalidArgumentException
 * will be raised.</p>
 * <p>This method as the side effeect of setting the receivers path and
 * name.  The this change can happen even if the write operation fails
 * with an exception.</p>
 */
- (void)writeToFile: (NSString *)path
{
  NSFileManager		*mgr = [NSFileManager defaultManager];
  NSMutableDictionary	*pList;
  NSDictionary		*entityPList;
  NSDictionary		*stProcPList;
  NSEnumerator		*entityEnum;
  NSEnumerator		*stProcEnum;
  NSString              *fileName;
  NSString              *extension;
  BOOL writeSingleFile;

  [self loadAllModelObjects];

  path = [path stringByStandardizingPath];
  extension = [path pathExtension];

  if ([extension isEqualToString: @"eomodeld"] == NO
      && [extension isEqualToString: @"eomodel"] == NO)
    {
      path = [path stringByAppendingPathExtension: @"eomodeld"];
      extension = [path pathExtension];
    }
  
  writeSingleFile = [extension isEqualToString: @"eomodel"] ? YES : NO;

  /*
   * Don't copy directories. EOModelEditor and NSDocument do all the magic for us.
   */
  /*
  if ([mgr fileExistsAtPath: path])
  {
    NSString *backupPath;
    backupPath = [path stringByAppendingString: @"~"];
    
    if ([mgr fileExistsAtPath: backupPath])
    {
      if ([mgr removeFileAtPath: backupPath handler: nil] == NO)
	    {
	      [NSException raise: NSInvalidArgumentException
                    format: @"Could not remove %@", backupPath];
	    }
    }
    
    if ([mgr movePath: path toPath: backupPath handler: nil] == NO)
    {
      [NSException raise: NSInvalidArgumentException
                  format: @"Could not move %@ to %@", path, backupPath];
    }
  }
*/
  [self _setPath: path];

  pList = [NSMutableDictionary dictionaryWithCapacity: 10];

  [self encodeIntoPropertyList: pList];

  // create an dir only if we have none.
  if (![mgr fileExistsAtPath: path])
  {
    
    if (writeSingleFile == NO
        && [mgr createDirectoryAtPath: path attributes: nil] == NO)
    {
      [NSException raise: NSInvalidArgumentException
                  format: @"Could not create directory: %@", path];
    }
  }

  [self _deleteTrashAtPath:path];
  
  entityEnum = [[pList objectForKey: @"entities"] objectEnumerator];
  while (writeSingleFile == NO
	 && (entityPList = [entityEnum nextObject]))
    {
      NSString *fileName;

      fileName = [path stringByAppendingPathComponent:
			 [NSString stringWithFormat: @"%@.plist",
				   [entityPList objectForKey: @"name"]]];
      
      if ([self _writePlist:entityPList toFile:fileName] == NO)
	{
	  [NSException raise: NSInvalidArgumentException
		       format: @"Could not create file: %@", fileName];
	}
    }

  stProcEnum = [[pList objectForKey: @"storedProcedures"] objectEnumerator];
  while (writeSingleFile == NO
	 && (stProcPList = [stProcEnum nextObject]))
    {
      NSString *fileName;

      fileName = [stProcPList objectForKey: @"name"];
      fileName = [fileName stringByAppendingPathExtension: @"storedProcedure"];
      fileName = [path stringByAppendingPathComponent: fileName];
      if ([self _writePlist:stProcPList toFile:fileName] == NO)
	{
	  [NSException raise: NSInvalidArgumentException
		       format: @"Could not create file: %@", fileName];
	}
    }

  if (writeSingleFile == NO)
    {
      fileName = [path stringByAppendingPathComponent: @"index.eomodeld"];
      [pList removeAllObjects];
      [self encodeTableOfContentsIntoPropertyList: pList];
    }
  else
    {
      fileName = path;
    }

  if ([self _writePlist:pList toFile:fileName] == NO)
    {
      [NSException raise: NSInvalidArgumentException
		   format: @"Could not create file: %@", fileName];
    }
  if (writeSingleFile == NO)
    {
      NSString *connectionSettingsPath = [path stringByAppendingPathComponent: @"connectionSettings.plist"];
      if ([self _writePlist: [self connectionDictionary] toFile: connectionSettingsPath] == NO)
	{
	  [NSException raise: NSInvalidArgumentException
		      format: @"Could not create file: %@", fileName];
	}
    }
}

@end

@implementation EOModel (EOModelPropertyList)

- (id)initWithTableOfContentsPropertyList: (NSDictionary *)tableOfContents
                                     path: (NSString *)path
{
  //OK
  NS_DURING
    {
      if ((self = [self init]))
        {
	  NSString *modelPath;
          NSString *versionString = nil;
          NSArray  *entities = nil;
          int i, count = 0;

          EOFLOGObjectLevelArgs(@"gsdb", @"tableOfContents=%@",
				tableOfContents);

	  /* The call to _setPath: also sets the name implicitly. */
	  modelPath = [[self class] _formatModelPath: path checkFileSystem: YES];
	  [self _setPath: modelPath];
          EOFLOGObjectLevelArgs(@"gsdb", @"name=%@ path=%@", _name, _path);

          versionString = [tableOfContents objectForKey: @"EOModelVersion"];
          if (versionString)
            _version = [versionString floatValue];
          else
            _version = 0; // dayers: is this correct?

          ASSIGN(_connectionDictionary,
                 [tableOfContents objectForKey: @"connectionDictionary"]);
          ASSIGN(_adaptorName, [tableOfContents objectForKey: @"adaptorName"]);
          ASSIGN(_userInfo, [tableOfContents objectForKey: @"userInfo"]);

          if (!_userInfo)
            {
              ASSIGN(_userInfo,
                     [tableOfContents objectForKey:@"userDictionary"]);
            }

          ASSIGN(_internalInfo,
                 [tableOfContents objectForKey: @"internalInfo"]);
          ASSIGN(_docComment,[tableOfContents objectForKey:@"docComment"]);

          //VERIFY
          if (_version >= 2)
            {
              NSMutableDictionary *markSP = [NSMutableDictionary dictionary];
              NSArray *storedProcedures 
		= [tableOfContents objectForKey: @"storedProcedures"];

              count = [storedProcedures count];
              for (i = 0; i < count; i++)
                {
                  EOStoredProcedure *proc;
                  NSDictionary *plist;
                  NSString *fileName;
		  NSString *procName;
		  NSString *fullPath;

		  procName = [storedProcedures objectAtIndex: i];

                  fileName = [procName stringByAppendingPathExtension:
					 @"storedProcedure"];

		  fullPath = [_path stringByAppendingPathComponent: fileName];
                  plist 
		    = [NSDictionary dictionaryWithContentsOfFile: fullPath];

		  NSAssert2([procName isEqual: [plist objectForKey: @"name"]],
			    @"Name mismatch: index.plist: %@"
			    @" *.storedProcedure: %@", procName,
			    [plist objectForKey: @"name"]);
                  [markSP setObject: plist forKey: procName];

                  proc
		    = [EOStoredProcedure storedProcedureWithPropertyList: plist
					 owner: self];
                  [self addStoredProcedure: proc];
                }

              count = [_storedProcedures count];
              for (i = 0; i < count; i++)
                {
		  EOStoredProcedure *proc;
		  NSString *name;
		  NSDictionary *plist;

		  proc = [_storedProcedures objectAtIndex: i];
		  name = [proc name];
		  plist = [markSP objectForKey: name];

		  if (plist)
		    {
		      [proc awakeWithPropertyList: plist];
		    }
		}
            }
        
          entities = [tableOfContents objectForKey: @"entities"];
          count = [entities count];

          for (i=0; i<count; i++)
            {
	      NSDictionary *entityPList = [entities objectAtIndex: i];
              [self _addFakeEntityWithPropertyList: entityPList];
            }
        }
    }
  NS_HANDLER
    {
      NSLog(@"exception in EOModel initWithTableOfContentsPropertyList:path:");
      NSLog(@"exception=%@", localException);
      [localException raise];
    }
  NS_ENDHANDLER;

  return self;
}

- (void)encodeTableOfContentsIntoPropertyList:
  (NSMutableDictionary *)propertyList
{
  int i, count;
  NSMutableArray *entitiesArray;
  NSMutableArray *stProcArray;

  [propertyList setObject:
    [[NSNumber numberWithFloat: DEFAULT_MODEL_VERSION] stringValue]
		forKey: @"EOModelVersion"];

  if (_adaptorName)
    [propertyList setObject: _adaptorName
		  forKey: @"adaptorName"];

  if (_userInfo)
    [propertyList setObject: _userInfo
		  forKey: @"userInfo"];

  if (_docComment)
    [propertyList setObject: _docComment forKey: @"docComment"];

  /* Do not access _entities until cache is triggered */
  count = [[self entities] count];
  entitiesArray = [NSMutableArray arrayWithCapacity: count];
  [propertyList setObject: entitiesArray forKey: @"entities"];

  for (i = 0; i < count; i++)
    {
      NSMutableDictionary *entityPList;
      EOEntity *entity;
      EOEntity *parent;

      entity = [_entities objectAtIndex: i];
      entityPList = [NSMutableDictionary dictionaryWithCapacity: 2];

      [entityPList setObject: [entity className] forKey: @"className"];
      [entityPList setObject: [entity name] forKey: @"name"];

      parent = [entity parentEntity];
      if (parent)
        [entityPList setObject: [parent name] forKey: @"parent"];

      [entitiesArray addObject: entityPList];
    }

  stProcArray = [_storedProcedures valueForKey: @"name"];
  [propertyList setObject: stProcArray forKey: @"storedProcedures"];
}

- (id) initWithPropertyList: (NSDictionary *)propertyList
                      owner: (id)owner
{
  NS_DURING
    {
      if (!propertyList)
        [NSException raise: NSInvalidArgumentException
                     format: @"%@ -- %@ 0x%p: must not be the nil object",
                     NSStringFromSelector(_cmd),
                     NSStringFromClass([self class]),
                     self];

      if (![propertyList isKindOfClass: [NSDictionary class]])
        [NSException raise: NSInvalidArgumentException
	  format: @"%@ -- %@ 0x%p: must not be kind of NSDictionary class",
	  NSStringFromSelector(_cmd),
	  NSStringFromClass([self class]),
	  self];

      if ((self = [self init]))
        {
          int i, count;
          NSArray *propListEntities, *propListSt;
          NSMutableDictionary *markEntities =
            [NSMutableDictionary dictionaryWithCapacity: 10];
          NSMutableDictionary *markSP =
            [NSMutableDictionary dictionaryWithCapacity: 10];
          NSEnumerator *enumerator;
          EOEntity *entity;
          EOStoredProcedure *sp;
      
          _version = [[propertyList objectForKey: @"EOModelVersion"]
		       floatValue];
          _adaptorName = RETAIN([propertyList objectForKey: @"adaptorName"]);
          _connectionDictionary = RETAIN([propertyList objectForKey:
						   @"connectionDictionary"]);
          _userInfo = RETAIN([propertyList objectForKey: @"userInfo"]);
          _docComment = RETAIN([propertyList objectForKey: @"docComment"]);

          propListEntities = [propertyList objectForKey: @"entities"];
          propListSt = [propertyList objectForKey: @"storedProcedures"];
      
          _flags.errors = NO;
      
          count = [propListEntities count];
          for (i = 0; i < count; i++)
            {
              EOEntity *entity;
              NSDictionary *plist;
          
              plist = [propListEntities objectAtIndex: i];
              EOFLOGObjectLevelArgs(@"gsdb", @"plist=%@ [%@]",
				    plist, [plist class]);
          
              if (_version >= 2)
                {
                  NSString *fileName = [NSString stringWithFormat: @"%@.plist",
                                                 [plist objectForKey: @"name"]];
              
                  plist = [[NSString stringWithContentsOfFile:
                                       [_name stringByAppendingPathComponent:
						fileName]]
                            propertyList];
                }
          
              [markEntities setObject: plist
                            forKey: [plist objectForKey: @"name"]];
          
              entity 
		= AUTORELEASE([[EOEntity alloc] initWithPropertyList: plist
						owner: self]);
              [self addEntity: entity];
            }

          /* Do not access _entities until cache is triggered */
          enumerator = [[self entities] objectEnumerator];
          while ((entity = [enumerator nextObject]))
            {
              NS_DURING
                {
                  [entity awakeWithPropertyList:
			    [markEntities objectForKey: [entity name]]];
                }
              NS_HANDLER
                {
                  [NSException raise: NSInvalidArgumentException
                               format: @"%@ -- %@ 0x%p: exception in model '%@' during awakeWithPropertyList: of entity '%@': %@",
                               NSStringFromSelector(_cmd),
                               NSStringFromClass([self class]),
                               self,
                               [self name],
                               [entity name],
                               [localException reason]];
                } 
              NS_ENDHANDLER;
            }
      
          if (_version >= 2)
            {
              count = [propListSt count];
              for (i = 0; i < count; i++)
                {
                  EOStoredProcedure *st;
                  NSDictionary *plist;
                  NSString *fileName;
              
                  fileName = [NSString stringWithFormat: @"%@.storedProcedure",
                                       [[propListSt objectAtIndex: i]
					 objectForKey: @"name"]];

                  plist = [[NSString stringWithContentsOfFile:
                                       [_name stringByAppendingPathComponent:
						fileName]]
                            propertyList];

                  [markSP setObject: plist
			  forKey: [plist objectForKey: @"name"]];

                  st = [EOStoredProcedure storedProcedureWithPropertyList: plist
                                          owner: self];
                  [self addStoredProcedure: st];
                }
          
              enumerator = [_storedProcedures objectEnumerator];
              while ((sp = [enumerator nextObject]))
                [sp awakeWithPropertyList: [markSP objectForKey: [sp name]]];
            }
        }
    }
  NS_HANDLER
    {
      NSLog(@"exception in EOModel initWithPropertyList:owner:");
      NSLog(@"exception=%@", localException);
      [localException raise];
    }
  NS_ENDHANDLER;
  
  return self;
}

- (void)awakeWithPropertyList: (NSDictionary *)propertyList
{
  return;
}

- (void)encodeIntoPropertyList: (NSMutableDictionary *)propertyList
{
  int i, count;

  [propertyList setObject: 
		  [[NSNumber numberWithFloat: DEFAULT_MODEL_VERSION]
			     stringValue]
		forKey: @"EOModelVersion"];

  if (_name)
    [propertyList setObject: _name forKey: @"name"];
  if (_adaptorName)
    [propertyList setObject: _adaptorName forKey: @"adaptorName"];
  if (_adaptorClassName) 
    [propertyList setObject: _adaptorClassName forKey: @"adaptorClassName"];
  if (_connectionDictionary)
    [propertyList setObject: _connectionDictionary
		  forKey: @"connectionDictionary"];
  if (_userInfo)
    [propertyList setObject: _userInfo forKey: @"userInfo"];
  if (_internalInfo)
    [propertyList setObject: _internalInfo forKey: @"internalInfo"];
  if (_docComment)
    [propertyList setObject: _docComment forKey: @"docComment"];

  /* Do not access _entities until cache is triggered */
  count = [[self entities] count];

  if (count > 0)
    {
      NSMutableArray *entitiesArray = [NSMutableArray arrayWithCapacity: count];

      [propertyList setObject: entitiesArray forKey: @"entities"];

      for (i = 0; i < count; i++)
        {
	  NSMutableDictionary *entityPList = [NSMutableDictionary dictionary];
	  EOEntity *entity = [_entities objectAtIndex: i];
	  
	  [entity encodeIntoPropertyList: entityPList];
	  [entitiesArray addObject: entityPList];
        }
    }

  count = [_storedProcedures count];
  if (count > 0)
    {
      NSMutableArray *stArray = [NSMutableArray arrayWithCapacity: count];

      [propertyList setObject: stArray forKey: @"storedProcedures"];
      for (i = 0; i < count; i++)
        {
	  NSMutableDictionary *stPList = [NSMutableDictionary dictionary];
	  EOStoredProcedure *proc = [_storedProcedures objectAtIndex: i];

	  [proc encodeIntoPropertyList: stPList];
	  [stArray addObject: stPList];
        }
    }
}

@end

@implementation EOModel (EOModelHidden)

- (NSMutableDictionary *) _loadFetchSpecificationDictionaryForEntityNamed:(NSString *) entName
{
  NSEmitTODO();
  
  return nil;
}

-(void) _classDescriptionNeeded: (NSNotification *)notification
{
  //TODO
  NSString *notificationName = nil;



  notificationName = [notification name];

  EOFLOGObjectLevelArgs(@"gsdb", @"notificationName=%@", notificationName);

  if ([notificationName
	isEqualToString: EOClassDescriptionNeededForClassNotification])
    {
      Class aClass = [notification object];
      EOClassDescription *classDescription = nil;
      EOEntity *entity = nil;
      NSString *entityClassName = nil;

      EOFLOGObjectLevelArgs(@"gsdb", @"notification=%@ aClass=%@",
			    notification, aClass);
      NSAssert(aClass, @"No class");

      entity = [self _entityForClass: aClass];

      if (!entity)
        {
          NSAssert1((!GSObjCIsKindOf(aClass, [EOGenericRecord class])),
                    @"No entity for class %@", aClass);
        }
      else
        {
          Class entityClass=Nil;
          classDescription = [entity classDescriptionForInstances];
          EOFLOGObjectLevelArgs(@"gsdb", @"classDescription=%@",
				classDescription);

          entityClassName = [entity className];
          EOFLOGObjectLevelArgs(@"gsdb",@"entityClassName=%@",entityClassName);

          entityClass=NSClassFromString(entityClassName);
          NSAssert1(entityClass,@"No entity class named '%@'",entityClassName);

          [EOClassDescription registerClassDescription: classDescription
                              forClass: entityClass];
        }
    }
  else if ([notificationName
	     isEqualToString: EOClassDescriptionNeededForEntityNameNotification])
    {
      //OK
      EOClassDescription *classDescription;
      NSString *entityName = [notification object];
      EOEntity *entity = nil;
      NSString *entityClassName = nil;
      Class entityClass = Nil;

      EOFLOGObjectLevelArgs(@"gsdb", @"notification=%@", notification);
      EOFLOGObjectLevelArgs(@"gsdb", @"entityName=%@", entityName);

      NSAssert(entityName, @"No entity name");//??

      entity = [self entityNamed: entityName];
      NSAssert1(entity, @"No entity named %@", entityName);//??

      classDescription = [entity classDescriptionForInstances];
      EOFLOGObjectLevelArgs(@"gsdb", @"classDescription=%@", classDescription);

      entityClassName = [entity className];
      EOFLOGObjectLevelArgs(@"gsdb", @"entityClassName=%@", entityClassName);

      entityClass=NSClassFromString(entityClassName);
      NSAssert1(entityClass,@"No entity class named '%@'",entityClassName);

      [EOClassDescription registerClassDescription: classDescription
                          forClass:entityClass];//??
    }
  else if ([notificationName
	     isEqualToString: EOClassDescriptionNeededNotification])
    {
      //TODO
    }
  else
    {
      //TODO
    }


}

- (void) _resetPrototypeCache
{
  // TODO
  [self notImplemented: _cmd];
}

- (BOOL) isPrototypesEntity: (id)param0
{
  // TODO
  [self notImplemented: _cmd];
  return NO;
}

- (id) _instantiatedEntities
{
  // TODO
  [self notImplemented: _cmd];
  return nil;
}

- (void) _setPath: (NSString*)path
{
  //OK
  [self loadAllModelObjects];
  [self willChange];
  ASSIGN(_path, path);
  [self setName: [[path lastPathComponent] stringByDeletingPathExtension]];
}

- (EOEntity*) _entityForClass: (Class)aClass
{
  NSString *className;
  EOEntity *entity;



  NSAssert(aClass, @"No class");
  NSAssert(_entitiesByClass, @"No _entitiesByClass");

  className = NSStringFromClass(aClass);
  EOFLOGObjectLevelArgs(@"gsdb", @"className=%@", className);

  entity = NSMapGet(_entitiesByClass, className);
  EOFLOGObjectLevelArgs(@"gsdb", @"entity class=%@", [entity class]);

  if (entity)
    {
      entity = [self _verifyBuiltEntityObject: entity
                     named: nil];
      EOFLOGObjectLevelArgs(@"gsdb", @"entity=%@", entity);
    }
  else
    {
      EOFLOGObjectLevelArgs(@"gsdb", @"entity for class named=%@ not found",
			    className);
      EOFLOGObjectLevelArgs(@"gsdb", @"entities class names=%@",
			    NSAllMapTableKeys(_entitiesByClass));
      EOFLOGObjectLevelArgs(@"gsdb", @"entities entities names=%@",
			    NSAllMapTableValues(_entitiesByClass));
      EOFLOGObjectLevelArgs(@"gsdb", @"entities map=%@",
			    NSStringFromMapTable(_entitiesByClass));
    }



  return entity;
}

- (id) _childrenForEntityNamed: (id)param0
{
  // TODO [self notImplemented:_cmd];
  return nil;
}

- (void) _registerChild: (id)param0
             forParent: (id)param1
{
  // TODO [self notImplemented:_cmd];
  return;
}

- (void) _setInheritanceLinks: (id)param0
{
  // TODO
  [self notImplemented: _cmd];
  return;
}

/**
 * Before removing attributes we need to remove all references
 */

- (void) _removePropertiesReferencingProperty:(id) property
{
  NSUInteger refCount, refIdx = 0;
  NSArray * references = nil;

  references = [self referencesToProperty:property];
    
  refCount = [references count];
  
  for (; refIdx < refCount; refIdx++) {
    id refObj = [references objectAtIndex:refIdx];
    
    if ([refObj class] == GDL2_EOAttributeClass)
    {
      [[(EOAttribute*) refObj entity] removeAttribute:refObj];
    } else {
      [[(EORelationship*) refObj entity] removeRelationship:refObj];
    }
  }
    
}

/**
 * Before removing entities we need to remove all references
 */

- (void) _removePropertiesReferencingEntity:(EOEntity*) entity
{
  int i;
  
  for (i = 0; i < 2; i++) 
  {
    NSArray        * attrsOrRels;
    NSArray        * names = nil;
    NSUInteger index = 0;
    NSUInteger count = 0;
    
    if (i == 0)
    {
      attrsOrRels = [entity attributes];
    } else {
      attrsOrRels = [entity relationships];
    }
    // get name from the array
    names = [attrsOrRels resultsOfPerformingSelector:@selector(name)];
    
    for (count = [names count]; index < count; index++) 
    {
      id attrOrRel = nil;
      
      if (i == 0) {
        attrOrRel = [entity attributeNamed:[names objectAtIndex:index]];
      } else {
        attrOrRel = [entity relationshipNamed:[names objectAtIndex:index]];
      }
      
      if (attrOrRel) {
        [self _removePropertiesReferencingProperty:attrOrRel];
      }
      
    }
    
  }
  
}

- (void) _removeEntity: (EOEntity *)entity
{
  //should be ok
  NSString *entityName = nil;
  NSString *entityClassName = nil;

  if ([entity isKindOfClass:GDL2_EOEntityClass])
    {
      entityName = [entity name];
      entityClassName = [entity className];
    }
  else
    {
      entityName = [(NSDictionary *)entity objectForKey: @"name"];
      entityClassName = [(NSDictionary *)entity  objectForKey: @"className"];
    }

  [_entitiesByName removeObjectForKey: entityName];

  if (_entitiesByClass)
    NSMapRemove(_entitiesByClass, entityClassName);

  DESTROY(_entities);
}

- (EOEntity*) _addEntityWithPropertyList: (NSDictionary*)propertyList
{
  //OK
  id children = nil;
  EOEntity *entity = nil;

  NSAssert(propertyList, @"no propertyList");
  EOFLOGObjectLevelArgs(@"gsdb", @"propertyList=%@", propertyList);

  entity = AUTORELEASE([[EOEntity alloc] initWithPropertyList: propertyList
					 owner: self]);

  NSAssert2([entity className], @"Entity %p named %@ has no class name",
	    entity, [entity name]);
  entity = [self _addEntity: entity];

  children = [self _childrenForEntityNamed: [entity name]];

  if (children)
    {
      [self notImplemented: _cmd];//TODO
      //may be: self  _registerChild:(id)param0
      //             forParent:entity...
    }
  [entity awakeWithPropertyList:propertyList];

  [[NSNotificationCenter defaultCenter]
    postNotificationName: @"EOEntityLoadedNotification"
    object: entity];

  return entity;
}

- (void) _addFakeEntityWithPropertyList: (NSDictionary*)propertyList
{
  //OK
  NSString *entityName;
  NSString *className;

  NSAssert(propertyList, @"no propertyList");

  entityName = [propertyList objectForKey: @"name"];
  className = [propertyList objectForKey: @"className"];

  NSAssert1(entityName, @"No entity name in %@", propertyList);
  NSAssert1(className, @"No class name in %@", propertyList);

  [self _setEntity: propertyList
        forEntityName: entityName
        className: className];

  DESTROY(_entities); //To force rebuild
}

- (id) _addEntity: (EOEntity*)entity
{
  //Seems OK
  NSString *entityClassName;

  NSAssert(entity, @"No entity to add");
  EOFLOGObjectLevelArgs(@"gsdb", @"model _addEntity=%@", [entity name]);

  entityClassName = [entity className];
  NSAssert2(entityClassName, @"Entity %p named %@ has no class name",
	    entity, [entity name]);

  //May be returning a previous entity of that name if any ?
  [self _setEntity: entity
        forEntityName: [entity name]
        className: entityClassName];
  [entity _setModel: self];

  return entity;
}

//entity can be a EOEntity or an entity PList
- (void) _setEntity: (id)entity
      forEntityName: (NSString*)entityName
	  className: (NSString*)className
{
  NSAssert(entityName, @"No entity name");
  NSAssert(className, @"No class name");

  NSAssert1([_entitiesByName objectForKey: entityName] == nil,
	    @"Entity '%@' is already registered with this model",
	    entityName);
  //Seems OK
  [_entitiesByName setObject: entity
                   forKey: entityName];

  NSAssert(_entitiesByClass, @"No entities by class");

  if (NSMapGet(_entitiesByClass, className))
    NSMapRemove(_entitiesByClass, className);

  NSMapInsertIfAbsent(_entitiesByClass, className, entity);
}

@end

@implementation EOModel (EOModelEditing)

- (void) setName: (NSString *)name
{
  if (![name isEqualToString: _name])
    {
      EOModelGroup *group;

      AUTORELEASE(RETAIN(self));

      group = [self modelGroup];
      if (group)
	{
	  [group removeModel: self];
	}

      [self willChange];
      ASSIGNCOPY(_name, name);

      if (group)
	{
	  [group addModel: self];
	}
    }
}

- (void) setAdaptorName: (NSString *)adaptorName
{
  [self willChange];
  ASSIGNCOPY(_adaptorName, adaptorName);
}

- (void) setConnectionDictionary: (NSDictionary *)connectionDictionary
{
  [self willChange];
  ASSIGN(_connectionDictionary, connectionDictionary);
}

- (void) setUserInfo: (NSDictionary *)userInfo
{
  [self willChange];
  ASSIGN(_userInfo, userInfo);
}

- (void) setDocComment: (NSString *)docComment
{
  [self willChange];
  ASSIGNCOPY(_docComment, docComment);
}

- (void) addEntity: (EOEntity *)entity
{
  NSString *entityName = [entity name];
  //  void *entitiesClass;
  //  int count;
  NSString *className = nil;
  
  if (!entity)
  {
    [NSException raise: NSInvalidArgumentException
                format: @"%s entity is nil.", __PRETTY_FUNCTION__];
  }
  
  if (([entity model] != nil) && ([entity model] != self)) 
  {
    [NSException raise: NSInvalidArgumentException
                format: @"%s %@ already registered with model named '%@'", 
     __PRETTY_FUNCTION__,
     entityName, [[entity model] name]];
    
  }
  
  if ([_entitiesByName objectForKey:[entity name]]) 
  {
    [NSException raise: NSInvalidArgumentException
                format: @"%s Cannot register two entities with the same name", 
     __PRETTY_FUNCTION__];
    
  }
  
  [self willChange];
  
  /* _entities is sorted. we want a new one! */
  if (_entities) {
    DESTROY(_entities);
  }
  
  NSAssert(_entitiesByClass, @"No _entitiesByClass");
  
  className = [entity className];
  NSAssert1(className, @"No className in %@", entity);
  
  if (NSMapGet(_entitiesByClass, className))
    NSMapRemove(_entitiesByClass, className);
  
  NSMapInsertIfAbsent(_entitiesByClass, className, entity);
  
  [_entitiesByName setObject: entity 
                      forKey: entityName];
  [entity _setModel: self];
}

/**
 * Removes the entity without performing any referential integrity checking.
 */
- (void) removeEntity: (EOEntity *)entity
{
  // TODO: find out why removeEntity: and _removeEntity: exists
  [self _removeEntity:entity];
}

- (void) removeEntityAndReferences: (EOEntity *)entity
{
  NSArray      * subEntities;
  NSEnumerator * subEnumer;
  EOEntity     * subEntity;
  
  [self _removePropertiesReferencingEntity:entity];
  subEntities = [NSArray arrayWithArray:[entity subEntities]];
  subEnumer = [subEntities objectEnumerator];
                 
                 
  while ((subEntity = [subEnumer nextObject])) {
    [entity removeSubEntity:subEntity];
  }
    
  [self removeEntity: entity];
}

- (void)addStoredProcedure: (EOStoredProcedure *)storedProcedure
{
  if ([self storedProcedureNamed: [storedProcedure name]])
    [NSException raise: NSInvalidArgumentException
                 format: @"%@ -- %@ 0x%p: \"%@\" already registered as stored procedure name ",
                 NSStringFromSelector(_cmd),
                 NSStringFromClass([self class]),
                 self,
                 [storedProcedure name]];
  NSAssert(_storedProcedures, @"Uninitialised _storedProcedures!");
  [self willChange];
  [(NSMutableArray *)_storedProcedures addObject: storedProcedure];
}

- (void)removeStoredProcedure: (EOStoredProcedure *)storedProcedure
{
  NSAssert(_storedProcedures, @"Uninitialised _storedProcedures!");

  [self willChange];
  [(NSMutableArray *)_storedProcedures removeObject: storedProcedure];
}

- (void) setModelGroup: (EOModelGroup *)group
{


//call group _addSubEntitiesCache:
  _group = group;


}

- (void)loadAllModelObjects
{
  NSArray *entityNames = [_entitiesByName allKeys];
  NSUInteger i,n = [entityNames count];
  
  [self storedProcedures];
  
  for (i=0; i<n; i++)
  {
    NSString *name = [entityNames objectAtIndex: i];
    id entity = [_entitiesByName objectForKey: name];
    
    // the reference imp does not do verify here.
    [self _verifyBuiltEntityObject: entity
                             named: name];
    
    [entity _loadEntity];
  }
}

/**
 * Returns an array of flattened attributes and relationships in the receiver's
 * entities that reference property, or nil if nothing references it.
 */
- (NSArray *) referencesToProperty: (id)property
{
  // TODO test
  NSEnumerator *entityEnumerator = [[self entities] objectEnumerator];
  IMP enumNO=NULL;
  EOEntity *ent;
  NSMutableArray *refProps = [NSMutableArray array];
  
  while ((ent = GDL2_NextObjectWithImpPtr(entityEnumerator,&enumNO)))
  {
    NSEnumerator *propEnumerator = [[ent attributes] objectEnumerator];
    EOAttribute *attr;
    EORelationship *rel;
    IMP propEnumNO=NULL;
    
    while ((attr = GDL2_NextObjectWithImpPtr(propEnumerator,&propEnumNO)))
    {
      if ([attr referencesProperty:property])
	    {
        NSArray * newArray;
	      [refProps addObject:attr];
        
        newArray = [self referencesToProperty:attr];
        if ([newArray count] > 0) {
          [refProps addObjectsFromArray:newArray];
        }
	    }
    }
    
    propEnumerator = [[ent relationships] objectEnumerator];
    propEnumNO = NULL;
    while ((rel = GDL2_NextObjectWithImpPtr(propEnumerator, &propEnumNO)))
    {
      if ([rel referencesProperty:property])
	    {
        NSArray * newArray;
	      [refProps addObject:rel];
        
        newArray = [self referencesToProperty:rel];
        if ([newArray count] > 0) {
          [refProps addObjectsFromArray:newArray];
        }
	    }
    }
  }	
  
  return [refProps count] ? [NSArray arrayWithArray:refProps] : nil;
}

- (NSArray *) externalModelsReferenced
{
  // TODO;
  [self notImplemented: _cmd];

  return nil;
}

@end


@implementation EOModel (EOModelBeautifier)

- (void) beautifyNames
{
  NSArray  *listItems;
  NSString *newString = [NSString string];
  int	    anz, i, count;
 
  EOFLOGObjectFnStartOrCond2(@"ModelingClasses", @"EOModel");
  
  /* Makes the receiver's name conform to a standard convention. 
     Names that conform to this style are all lower-case except 
     for the initial letter of each embedded word other than the 
     first, which is upper case. Thus, "NAME" becomes "name", and 
     "FIRST_NAME" becomes "firstName". */
  
  if ((_name) && ([_name length] > 0))
    {
      listItems = [_name componentsSeparatedByString: @"_"];
      newString = [newString stringByAppendingString:
			       [(NSString *)[listItems objectAtIndex: 0]
					    lowercaseString]];
      anz = [listItems count];

      for (i = 1; i < anz; i++)
	{
	  newString = [newString stringByAppendingString:
				   [(NSString *)[listItems objectAtIndex: i]
						capitalizedString]];
	}

      // Exception abfangen
      NS_DURING
	{
	  // Model Name
	  [self setName: newString];

	  // Entites
	  /* Do not access _entities until cache is triggered */
	  if ([self entities] && (count = [_entities count]))
	    {
	      for (i = 0; i < count; i++)
		[(EOEntity *)[_entities objectAtIndex:i] beautifyName];
	    }
	}
      NS_HANDLER
	NSLog(@"%@ in Class: EOEntity , Method: beautifyName >> error : %@",
	      [localException name], [localException reason]);
      NS_ENDHANDLER;
    }

  EOFLOGObjectFnStopOrCond2(@"ModelingClasses", @"EOModel");
}

@end

@implementation EOModel (EOModelPrivate)
/**
 * Returns a string that can be uses as a model path to load or save
 * the model.  If chkFS is YES, then the path is searched.  If path
 * does not include a path extension then first .eomodeld checked.  If that
 * fails then .eomodel is searched.  Call this method to format the path
 * provided before saving the model with chkFS NO.  Call this method to
 * format the path provided before loading a model with chkFS YES.
 */
+ (NSString *) _formatModelPath: (NSString *)path checkFileSystem: (BOOL)chkFS
{
  NSFileManager *fileManager;
  NSString *lastPathComponent = nil;
  NSString *pathExtension = nil;
  NSString *searchPath = path;
  NSString *returnPath = path;

  lastPathComponent = [path lastPathComponent];
  pathExtension = [lastPathComponent pathExtension];

  if ([lastPathComponent isEqualToString: @"index.eomodeld"] == NO)
    {
      if ([pathExtension isEqualToString: @"eomodeld"] == NO)
	{
	  searchPath =
	      [searchPath stringByAppendingPathExtension: @"eomodeld"];
	}
      searchPath =
	  [searchPath stringByAppendingPathComponent: @"index.eomodeld"];
    }
  
  searchPath = [searchPath stringByStandardizingPath];

  if (chkFS==YES)
    {
      fileManager = [NSFileManager defaultManager];

      if ([fileManager fileExistsAtPath: searchPath] == YES)
        {
          returnPath = searchPath;
	}
      else
        {
	  searchPath = path;
          if ([pathExtension isEqualToString: @"eomodel"] == NO)
	    {
	      searchPath =
		  [searchPath stringByAppendingPathComponent: @"eomodel"];
	    }
	  searchPath = [searchPath stringByStandardizingPath];
	  if ([fileManager fileExistsAtPath: searchPath] == YES)
	    {
	      returnPath = searchPath;
	    }
	}
      NSAssert1(returnPath!=nil,@"No valid Model found at path:%@", path);
    }
  else 
    {
      returnPath = searchPath;
    }

  lastPathComponent = [returnPath lastPathComponent];
  if ([lastPathComponent isEqualToString: @"index.eomodeld"] == YES)
    {
      returnPath = [returnPath stringByDeletingLastPathComponent];
    }

  return returnPath;
}

-(NSDictionary*)propertyListForEntity:(id)entity
				 name:(NSString*)name
{
  NSDictionary* propList=nil;

  NSString* plistPathName = [[[self path] stringByAppendingPathComponent: name]
			      stringByAppendingPathExtension: @"plist"];

  EOFLOGObjectLevelArgs(@"gsdb", @"entity plistPathName =%@",
			plistPathName);
  
  propList = [NSDictionary dictionaryWithContentsOfFile: plistPathName];
  EOFLOGObjectLevelArgs(@"gsdb", @"entity propList=%@", propList);

  if (!propList)
    {
      if ([[NSFileManager defaultManager]
	    fileExistsAtPath: plistPathName])
	{
	  NSAssert1(NO,
		    @"%@ is not a dictionary or is not readable.",
		    plistPathName);
	}
      else
	{
	  propList = entity;
	  NSWarnLog(@"%@ doesn't exists. Using %@",
		    plistPathName, propList);
	}
    }
  return propList;  
}

- (EOEntity *) _verifyBuiltEntityObject: (id)entity
				  named: (NSString*)name
{
  if ([entity isKindOfClass:GDL2_EOEntityClass] == NO)
    {
      [EOObserverCenter suppressObserverNotification];

      NS_DURING
        {
          NSString *basePath = nil;
          NSDictionary *propList = nil;

          EOFLOGObjectLevelArgs(@"gsdb", @"name=%@", name);

          if (!name && [entity isKindOfClass: [NSDictionary class]])
            name = [entity objectForKey: @"name"];

          EOFLOGObjectLevelArgs(@"gsdb", @"name=%@", name);
          NSAssert1(name, @"No name for entity %@", entity);
          EOFLOGObjectLevelArgs(@"gsdb", @"[self path]=%@", [self path]);

          basePath = [self path]; 
          AUTORELEASE(RETAIN(entity)); //so it won't be lost in _removeEntity

          EOFLOGObjectLevelArgs(@"gsdb", @"basePath =%@", basePath);

	  if ([basePath hasSuffix: @"eomodel"])
	    {
	      propList = entity;
	    }
	  else
	    {
	      propList=[self propertyListForEntity:entity
			     name:name];
	    }

          [self _removeEntity: entity];
          EOFLOGObjectLevelArgs(@"gsdb", @"entity propList=%@", propList);

          entity = [self _addEntityWithPropertyList: propList];
        }
      NS_HANDLER
        {
          [EOObserverCenter enableObserverNotification];
          [localException raise];
        }
      NS_ENDHANDLER;
      [EOObserverCenter enableObserverNotification];
    }

  return entity;
}

/*
  This method rebuilds the caches:
  _entitiesByName, _subEntitiesCache, _entitiesByClass
  from _entities.  Insure this works for real entities and dictionaries
  which could be contained in _entities.  Also note that className need
  not be unique.
*/
- (void)_updateCache
{
  NSArray  *names;
  EOEntity *entity;
  NSString *className;
  unsigned int i,c;

  DESTROY(_subEntitiesCache);
  NSResetMapTable(_entitiesByClass);

  names = [_entities valueForKey: @"name"];
  DESTROY(_entitiesByName);
  _entitiesByName = [[NSMutableDictionary alloc] initWithObjects: _entities
						 forKeys: names];
  for (i = 0, c = [_entities count]; i < c; i++)
    {
      entity = [_entities objectAtIndex: i]; /* entity or dictionary */
      className = [entity valueForKey: @"className"];
      NSMapInsertIfAbsent(_entitiesByClass, className, entity);
    }
}
@end /* EOModel (EOModelPrivate) */

