/** 
   EORelationship.m <title>EORelationship</title>

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

   Author: Mirko Viviani <mirko.viviani@gmail.com>
   Date: February 2000

   Author: Manuel Guesdon <mguesdon@orange-concept.com>
   Date: October 2000

   $Revision: 27916 $
   $Date: 2009-02-18 15:47:26 +0100 (Mit, 18. Feb 2009) $

   <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"

RCS_ID("$Id: EORelationship.m 27916 2009-02-18 14:47:26Z ratmice $")

#ifdef GNUSTEP
#include <Foundation/NSArray.h>
#include <Foundation/NSException.h>
#include <Foundation/NSDebug.h>
#else
#include <Foundation/Foundation.h>
#endif

#ifndef GNUSTEP
#include <GNUstepBase/GNUstep.h>
#include <GNUstepBase/GSObjCRuntime.h>
#include <GNUstepBase/GSCategories.h>
#endif

#include <EOControl/EOObserver.h>
#include <EOControl/EOMutableKnownKeyDictionary.h>
#include <EOControl/EONSAddOns.h>
#include <EOControl/EODebug.h>

#include <EOAccess/EOModel.h>
#include <EOAccess/EOAttribute.h>
#include <EOAccess/EOEntity.h>
#include <EOAccess/EOStoredProcedure.h>
#include <EOAccess/EORelationship.h>
#include <EOAccess/EOJoin.h>
#include <EOAccess/EOExpressionArray.h>

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

@interface EORelationship (EORelationshipPrivate)
- (void)_setInverseRelationship: (EORelationship *)relationship;
@end


@implementation EORelationship

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

      GDL2_EOAccessPrivateInit();
    }
}

+ (id) relationshipWithPropertyList: (NSDictionary *)propertyList
                              owner: (id)owner
{
  return AUTORELEASE([[self alloc] initWithPropertyList: propertyList
				   owner: owner]);
}

- (id)init
{
//OK
  if ((self = [super init]))
    {
      /*
      _sourceNames = [NSMutableDictionary new];
      _destinationNames = [NSMutableDictionary new];
      _userInfo = [NSDictionary new];
      _sourceToDestinationKeyMap = [NSDictionary new];
      */
      _joins = [NSMutableArray new];

    }

  return self;
}

- (void)dealloc
{
  [self _flushCache];

  DESTROY(_name);
  DESTROY(_qualifier);
  DESTROY(_sourceNames);
  DESTROY(_destinationNames);
  DESTROY(_userInfo);
  DESTROY(_internalInfo);
  DESTROY(_docComment);
  DESTROY(_joins);
  DESTROY(_sourceToDestinationKeyMap);
  DESTROY(_sourceRowToForeignKeyMapping);

  DESTROY(_definitionArray);

  _entity = nil;
  _destination = nil;
  
  [super dealloc];
}

- (unsigned)hash
{
  return [_name hash];
}

- (id) initWithPropertyList: (NSDictionary *)propertyList
                      owner: (id)owner
{
  //Near OK
  if ((self = [self init]))
    {
      NSString *joinSemanticString = nil;
      EOModel *model;
      NSString* destinationEntityName = nil;
      EOEntity* destinationEntity = nil;
      NSString* deleteRuleString = nil;
      NSString* relationshipName;

      EOFLOGObjectFnStart();

      model = [owner model];
      relationshipName = [propertyList objectForKey: @"name"];

      /* so setName: can validate against the owner */
      [self setEntity: owner];
      [self setName: relationshipName]; 

      destinationEntityName = [propertyList objectForKey: @"destination"];

      if (destinationEntityName) //If not, this is because it's a definition
        {
          destinationEntity = [model entityNamed: destinationEntityName];

          _destination = destinationEntity;
        }

      [self setToMany: [[propertyList objectForKey: @"isToMany"]
			 isEqual: @"Y"]];
      [self setIsMandatory: [[propertyList objectForKey: @"isMandatory"]
			      isEqual:@"Y"]];
      [self setOwnsDestination: [[propertyList
				   objectForKey: @"ownsDestination"]
				  isEqual: @"Y"]];
      [self setPropagatesPrimaryKey: [[propertyList
					objectForKey: @"propagatesPrimaryKey"]
				       isEqual: @"Y"]];
      [self setIsBidirectional: [[propertyList objectForKey: @"isBidirectional"]
				  isEqual: @"Y"]];

      [self setUserInfo: [propertyList objectForKey: @"userInfo"]];

      if(!_userInfo)
        [self setUserInfo: [propertyList objectForKey: @"userDictionary"]];

      [self setInternalInfo: [propertyList objectForKey: @"internalInfo"]];
      [self setDocComment: [propertyList objectForKey: @"docComment"]];

      joinSemanticString = [propertyList objectForKey: @"joinSemantic"];
      if (joinSemanticString)
        {
          if ([joinSemanticString isEqual: @"EOInnerJoin"])
            [self setJoinSemantic: EOInnerJoin];
          else if ([joinSemanticString isEqual: @"EOFullOuterJoin"])
            [self setJoinSemantic: EOFullOuterJoin];
          else if ([joinSemanticString isEqual: @"EOLeftOuterJoin"])
            [self setJoinSemantic: EOLeftOuterJoin];
          else if ([joinSemanticString isEqual: @"EORightOuterJoin"])
            [self setJoinSemantic: EORightOuterJoin];
          else
            {
              EOFLOGObjectLevelArgs(@"EORelationship", @"Unknown joinSemanticString=%@. entityName=%@ relationshipName=%@",
				    joinSemanticString,
				    [(EOEntity*)owner name],
				    relationshipName);
              NSEmitTODO(); //TODO
              [self notImplemented: _cmd]; //TODO
            }
        }
      else
        {
          if (destinationEntityName)
            {
              EOFLOGObjectLevelArgs(@"EORelationship", @"!joinSemanticString but destinationEntityName. entityName=%@ relationshipName=%@",
				    [(EOEntity*)owner name],
				    relationshipName);
              NSEmitTODO(); //TODO
              [self notImplemented: _cmd]; //TODO
            }
        }

      deleteRuleString = [propertyList objectForKey: @"deleteRule"];
      EOFLOGObjectLevelArgs(@"EORelationship", @"entityName=%@ relationshipName=%@ deleteRuleString=%@",
			    [(EOEntity*)owner name],
			    relationshipName,
			    deleteRuleString);

      if (deleteRuleString)
        {
          EODeleteRule deleteRule = [self _deleteRuleFromString:
					    deleteRuleString];
          EOFLOGObjectLevelArgs(@"EORelationship",
				@"entityName=%@ relationshipName=%@ deleteRule=%d",
				[(EOEntity*)owner name],
				relationshipName,
				(int)deleteRule);
          NSAssert2(deleteRule >= 0 && deleteRule < 4,
		    @"Bad deleteRule numeric value: %@ (%d)",
		    deleteRuleString,
		    deleteRule);

          [self setDeleteRule: deleteRule];
        }
    }

  EOFLOGObjectFnStop();

  return self;
}

- (void)awakeWithPropertyList: (NSDictionary *)propertyList  //TODO
{
  //OK for definition
  NSString *definition;

  EOFLOGObjectFnStart();

  EOFLOGObjectLevelArgs(@"EORelationship", @"self=%@", self);

  definition = [propertyList objectForKey: @"definition"];

  EOFLOGObjectLevelArgs(@"EORelationship", @"definition=%@", definition);

  if (definition)
    {
      [self setDefinition: definition];
    }
  else
    {
      NSString *dataPath = [propertyList objectForKey: @"dataPath"];

      EOFLOGObjectLevelArgs(@"EORelationship", @"dataPath=%@", dataPath);

      if (dataPath)
        {
          NSEmitTODO(); //TODO
          [self notImplemented: _cmd]; // TODO
        }
      else
        {
          NSArray *joins = [propertyList objectForKey: @"joins"];
          int count = [joins count];

          EOFLOGObjectLevelArgs(@"EORelationship", @"joins=%@", joins);

          if (count > 0)
            {
              int i;

              for (i = 0; i < count; i++)
                {
                  NSDictionary *joinPList;
                  NSString *joinSemantic;
                  NSString *sourceAttributeName;
                  EOAttribute *sourceAttribute;
                  EOEntity *destinationEntity;
                  NSString *destinationAttributeName = nil;
                  EOAttribute *destinationAttribute = nil;
                  EOJoin *join = nil;

                  joinPList = [joins objectAtIndex: i];
                  joinSemantic = [joinPList objectForKey: @"joinSemantic"];

                  sourceAttributeName = [joinPList objectForKey:
						     @"sourceAttribute"];
                  sourceAttribute = [_entity attributeNamed:
					       sourceAttributeName];

                  NSAssert4(sourceAttribute, @"No sourceAttribute named \"%@\" in entity \"%@\" in relationship %@\nEntity: %@",
                            sourceAttributeName,
                            [_entity name],
                            self,
                            _entity);

                  destinationEntity = [self destinationEntity];
                  NSAssert3(destinationEntity,@"No destination entity for relationship named '%@' in entity named '%@': %@",
                            [self name],
                            [[self entity]name],
                            self);
                  destinationAttributeName = [joinPList
					       objectForKey:
						 @"destinationAttribute"];
                  destinationAttribute = [destinationEntity
					   attributeNamed:
					     destinationAttributeName];

                  NSAssert4(destinationAttribute, @"No destinationAttribute named \"%@\" in entity \"%@\" in relationship %@\nEntity: %@",
                            destinationAttributeName,
                            [destinationEntity name],
                            self,
                            destinationEntity);

                  NS_DURING
                    {
                      join = [EOJoin joinWithSourceAttribute: sourceAttribute
				     destinationAttribute: destinationAttribute];
                    }
                  NS_HANDLER
                    {
                      [NSException raise: NSInvalidArgumentException
                                   format: @"%@ -- %@ 0x%x: cannot create join for relationship '%@': %@", 
                                   NSStringFromSelector(_cmd), 
                                   NSStringFromClass([self class]), 
                                   self, 
                                   [self name], 
                                   [localException reason]];
                    }
                  NS_ENDHANDLER;

                  EOFLOGObjectLevelArgs(@"EORelationship", @"join=%@", join);

                  [self addJoin: join];
                }
            }
          /*
            NSArray *array;
            NSEnumerator *enumerator;
            EOModel *model = [_entity model];
            id joinPList;
            
            if(_destination)
            {
            id destinationEntityName = [_destination autorelease];
            
            _destination = [[model entityNamed:destinationEntityName] retain];
            if(!_destination)
            {
          NSEmitTODO();  //TODO
            [self notImplemented:_cmd]; // TODO
            }
            }
          */
        }
    }
  /* ??
  if(!(_destination || _definitionArray))
    {
          NSEmitTODO();  //TODO
      [self notImplemented:_cmd]; // TODO
    };
  */

  EOFLOGObjectFnStop();
}

- (void)encodeIntoPropertyList: (NSMutableDictionary *)propertyList
{
  NS_DURING //Just for debugging
    {
      //VERIFY
      [propertyList setObject: [self name]
                    forKey: @"name"];
      
      if ([self isFlattened])
        {
          NSString *definition = [self definition];
          NSAssert(definition,@"No definition");
          [propertyList setObject: definition
                        forKey: @"definition"];
        }
      else
        {
          [propertyList setObject: ([self isToMany] ? @"Y" : @"N")
                        forKey: @"isToMany"];
          if ([self destinationEntity])
            {
              NSAssert2([[self destinationEntity] name],
                        @"No entity name in relationship named %@ entity named %@",
                        [self name],
                        [[self entity]name]);
              [propertyList setObject: [[self destinationEntity] name] // if we put entity, it loops !!
                            forKey: @"destination"];  
            };
        }
      
      if ([self isMandatory])
        [propertyList setObject: @"Y"
                      forKey: @"isMandatory"];
      
      if ([self ownsDestination])
        {
          NSEmitTODO(); //TODO
        }
      
      if ([self propagatesPrimaryKey])
        {
          NSEmitTODO(); //TODO
        }
      
      {
        int joinsCount = [_joins count];
        
        if (joinsCount > 0)
          {
            NSMutableArray *joinsArray = [NSMutableArray array];
            int i = 0;
            
            for(i = 0; i < joinsCount; i++)
              {
                NSMutableDictionary *joinDict = [NSMutableDictionary dictionary];
                EOJoin *join = [_joins objectAtIndex: i];
                
                NSAssert([[join sourceAttribute] name],
                         @"No source attribute name");

                [joinDict setObject: [[join sourceAttribute] name]
                          forKey: @"sourceAttribute"];

                NSAssert([[join destinationAttribute] name],
                         @"No destination attribute name");
                [joinDict setObject: [[join destinationAttribute] name]
                          forKey: @"destinationAttribute"];

                [joinsArray addObject: joinDict];
              }
            
            [propertyList setObject: joinsArray
                          forKey: @"joins"]; 
          }
        
        NSAssert([self joinSemanticString],
                 @"No joinSemanticString");
        [propertyList setObject: [self joinSemanticString]
                      forKey: @"joinSemantic"];
      }
    }
  NS_HANDLER
    {
      NSLog(@"exception in EORelationship encodeIntoPropertyList: self=%p class=%@",
	    self, [self class]);
      NSDebugMLog(@"exception in EORelationship encodeIntoPropertyList: self=%p class=%@",
	    self, [self class]);
      NSLog(@"exception=%@", localException);
      NSDebugMLog(@"exception=%@", localException);
      [localException raise];
    }
  NS_ENDHANDLER;
}

- (NSString *)description
{
  NSString *dscr = nil;

  NS_DURING //Just for debugging
    {
      dscr = [NSString stringWithFormat: @"<%s %p - name=%@ entity=%@ destinationEntity=%@ definition=%@",
		       object_get_class_name(self),
		       (void*)self,
		       [self name],
		       [[self entity]name],
		       [[self destinationEntity] name],
		       [self definition]];

      dscr = [dscr stringByAppendingFormat: @" userInfo=%@",
                   [self userInfo]];
      dscr = [dscr stringByAppendingFormat: @" joins=%@",
                   [self joins]];
      dscr = [dscr stringByAppendingFormat: @" sourceAttributes=%@",
                   [self sourceAttributes]];
      dscr = [dscr stringByAppendingFormat: @" destinationAttributes=%@",
                   [self destinationAttributes]];

      /*TODO  dscr = [dscr stringByAppendingFormat:@" componentRelationships=%@",
        [self componentRelationships]];*/

      dscr = [dscr stringByAppendingFormat: @" isCompound=%s isFlattened=%s isToMany=%s isBidirectional=%s>",
		   ([self isCompound] ? "YES" : "NO"),
                   ([self isFlattened] ? "YES" : "NO"),
                   ([self isToMany] ? "YES" : "NO"),
                   ([self isBidirectional] ? "YES" : "NO")];
    }
  NS_HANDLER
    {
      NSLog(@"exception in EORelationship description: self=%p class=%@",
	    self, [self class]);
      NSDebugMLog(@"exception in EORelationship description: self=%p class=%@",
                  self, [self class]);
      NSLog(@"exception=%@", localException);
      NSDebugMLog(@"exception=%@", localException);

      [localException raise];
    }
  NS_ENDHANDLER;

  return dscr;
}

- (NSString *)name
{
  return _name;
}

/** Returns the relationship's source entity. **/
- (EOEntity *)entity
{
  return _entity;
}

/** Returns the relationship's destination entity (direct destination entity or 
destination entity of the last relationship in definition. **/
- (EOEntity *)destinationEntity
{
  //OK
  // May be we could cache destination ? Hard to do because klast relationship may have its destination entity change.
  EOEntity *destinationEntity = _destination;

  if (!destinationEntity)
    {
      if ([self isFlattened])
        {
          EORelationship *lastRelationship = [_definitionArray lastObject];

          destinationEntity = [lastRelationship destinationEntity];

          NSAssert3(destinationEntity, @"No destinationEntity in last relationship: %@ of relationship %@ in entity %@",
                    lastRelationship, self, [_entity name]);
        }
      else
        {
	  [self _joinsChanged];
	  destinationEntity = _destination;
	}
    }
  else if ([destinationEntity isKindOfClass: [NSString class]] == YES)
    destinationEntity = [[_entity model] 
			  entityNamed: (NSString*)destinationEntity];

  return destinationEntity;
}

- (BOOL) isParentRelationship
{
  BOOL isParentRelationship = NO;
  /*EOEntity *destinationEntity = [self destinationEntity];
    EOEntity *parentEntity = [_entity parentEntity];*///nil

  NSEmitTODO();  //TODO
  // [self notImplemented:_cmd]; //TODO...

  return isParentRelationship;
}

/** Returns YES when the relationship traverses at least two entities 
(exemple: aRelationship.anotherRelationship), NO otherwise. 
**/
- (BOOL)isFlattened
{
  if (_definitionArray)
    return [_definitionArray isFlattened];
  else
    return NO;
}

/** return YES if the relation if a to-many one, NO otherwise (please read books 
to know what to-many mean :-)  **/
- (BOOL)isToMany
{
  return _flags.isToMany;
}

/** Returns YES if the relationship have more than 1 join (i.e. join on more that one (sourceAttribute/destinationAttribute), NO otherwise (1 or less join) **/

- (BOOL)isCompound
{
  //OK
  return [_joins count] > 1;
}

- (NSArray *)joins
{
  return _joins;
}

- (NSArray *)sourceAttributes
{
  //OK
  if (!_sourceAttributes)
    {
      int i, count = [_joins count];

      _sourceAttributes = [NSMutableArray new];

      for (i = 0; i < count; i++)
        {
          EOJoin *join = [_joins objectAtIndex: i];
          [(NSMutableArray*)_sourceAttributes addObject:
			      [join sourceAttribute]];
        }
    }

  return _sourceAttributes;
}

- (NSArray *)destinationAttributes
{
  //OK
  if (!_destinationAttributes)
    {
      int i, count = [_joins count];

      _destinationAttributes = [NSMutableArray new];

      for (i = 0; i < count; i++)
        {
          EOJoin *join = [_joins objectAtIndex: i];

          [(NSMutableArray *)_destinationAttributes addObject:
			      [join destinationAttribute]];
        }
    }

  return _destinationAttributes;
}

- (EOJoinSemantic)joinSemantic
{
  return _joinSemantic;
}

- (NSString*)joinSemanticString
{
  NSString *joinSemanticString = nil;

  switch ([self joinSemantic])
    {
    case EOInnerJoin:
      joinSemanticString = @"EOInnerJoin";
      break;
    case EOFullOuterJoin:
      joinSemanticString = @"EOFullOuterJoin";
      break;
    case EOLeftOuterJoin:
      joinSemanticString = @"EOLeftOuterJoin";
      break;
    case EORightOuterJoin:
      joinSemanticString = @"EORightOuterJoin";
      break;
    default:
      NSAssert1(NO, @"Unknwon join semantic code %d",
		(int)[self joinSemantic]);
      break;
    }

  return joinSemanticString;
}

/**
 * Returns the array of relationships composing this flattend relationship.
 * Returns nil of the reciever isn't flattend.
 */
- (NSArray *)componentRelationships
{
  /* FIXME:TODO: Have this method deterimne the components dynamically
   without caching them in the ivar.  Possibly add some tracing code to
   see if caching the values can actually improve performance.
   (Unlikely that it's worth the trouble this may cause for entity
   edititng). */
  if (!_componentRelationships)
    {
      return _definitionArray; //OK ??????
      NSEmitTODO();  //TODO
      [self notImplemented: _cmd]; //TODO
    }

  return _componentRelationships;
}

- (NSDictionary *)userInfo
{
  return _userInfo;
}

- (NSString *)docComment
{
  return _docComment;
}

- (NSString *)definition
{
  //OK
  NSString *definition = nil;

  NS_DURING //Just for debugging
    {
      definition = [_definitionArray valueForSQLExpression: nil];
    }
  NS_HANDLER
    {
      NSLog(@"exception in EORelationship definition: self=%p class=%@",
	    self, [self class]);
      NSLog(@"exception in EORelationship definition: self=%@ _definitionArray=%@",
	    self, _definitionArray);
      NSLog(@"exception=%@", localException);

      [localException raise];
    }
  NS_ENDHANDLER;

  return definition;
}

/** Returns the value to use in an EOSQLExpression. **/
- (NSString*) valueForSQLExpression: (EOSQLExpression*)sqlExpression
{
  EOFLOGObjectLevelArgs(@"EORelationship", @"EORelationship %p", self);

  NSEmitTODO();  //TODO
//  return [self notImplemented:_cmd]; //TODO
//return name ??

  return [self name];
}

- (BOOL)referencesProperty: (id)property
{
  BOOL referencesProperty = NO;
  NSArray *srcAttribs = [self sourceAttributes];
  NSArray *destAttribs = [self destinationAttributes];
  NSArray *compRels = [self componentRelationships];

  NSEmitTODO();  //TODO
  EOFLOGObjectLevelArgs(@"EORelationship", @"in referencesProperty:%@",
			property);

  referencesProperty =
	  	((srcAttribs
		  && [srcAttribs indexOfObject: property] != NSNotFound)
		|| (destAttribs
		    && [destAttribs indexOfObject: property] != NSNotFound)
		|| (compRels
		    && [compRels indexOfObject: property] != NSNotFound));

  return referencesProperty;
}

- (EODeleteRule)deleteRule
{
  EOFLOGObjectFnStart();
  EOFLOGObjectFnStop();

  return _flags.deleteRule;
}

- (BOOL)isMandatory
{
  return _flags.isMandatory;
}

- (BOOL)propagatesPrimaryKey
{
  return _flags.propagatesPrimaryKey;
}

- (BOOL)isBidirectional
{
  return _flags.isBidirectional;
}

- (BOOL)isReciprocalToRelationship: (EORelationship *)relationship
{
  //Should be OK
  //Ayers: Review
  BOOL isReciprocal = NO;
  EOEntity *entity;
  EOEntity *relationshipDestinationEntity = nil;

  EOFLOGObjectFnStart();

  entity = [self entity]; //OK
  relationshipDestinationEntity = [relationship destinationEntity];

  EOFLOGObjectLevelArgs(@"EORelationship", @"entity %p name=%@",
			entity, [entity name]);
  EOFLOGObjectLevelArgs(@"EORelationship",
			@"relationshipDestinationEntity %p name=%@",
			relationshipDestinationEntity,
			[relationshipDestinationEntity name]);

  if (entity == relationshipDestinationEntity) //Test like that ?
    {
      if ([self isFlattened]) //OK
        {
          if ([relationship isFlattened]) //OK
            {
              //Now compare each components in reversed order 
              NSArray *selfComponentRelationships =
		[self componentRelationships];
              NSArray *relationshipComponentRelationships =
		[relationship componentRelationships];
              int selfComponentRelationshipsCount =
		[selfComponentRelationships count];
              int relationshipComponentRelationshipsCount =
		[relationshipComponentRelationships count];

              //May be we can imagine that they may not have the same number of components //TODO
              if (selfComponentRelationshipsCount
		  == relationshipComponentRelationshipsCount) 
                {
                  int i, j;
                  BOOL foundEachInverseComponent = YES;

                  for(i = (selfComponentRelationshipsCount - 1), j = 0;
		      foundEachInverseComponent && i >= 0;
		      i--, j++)
                    {
                      EORelationship *selfRel =
			[selfComponentRelationships objectAtIndex: i];
                      EORelationship *relationshipRel =
			[relationshipComponentRelationships objectAtIndex: j];

                      foundEachInverseComponent =
			[selfRel isReciprocalToRelationship: relationshipRel];
                    }

                  if (foundEachInverseComponent)
                    isReciprocal = YES;
                }
            }
          else
            {
              //Just do nothing and try another relationship.
              // Is it the good way ?
              /*
              NSEmitTODO(); //TODO
              NSDebugMLog(@"entity %p name=%@ self name=%@ relationship name=%@ relationshipDestinationEntity %p name=%@",
                          entity, [entity name],
                          [self name],
                          [relationship name],
                          relationshipDestinationEntity,
                          [relationshipDestinationEntity name]);
              [self notImplemented: _cmd]; //TODO
              */
            }
        }
      else
        {
          //WO doens't test inverses entity 
          EOEntity *relationshipEntity = [relationship entity];
          EOEntity *destinationEntity = [self destinationEntity];

          EOFLOGObjectLevelArgs(@"EORelationship",
				@"relationshipEntity %p name=%@",
				relationshipEntity, [relationshipEntity name]);
          EOFLOGObjectLevelArgs(@"EORelationship",
				@"destinationEntity %p name=%@",
				destinationEntity, [destinationEntity name]);

          if (relationshipEntity == destinationEntity)
            {
              NSArray *joins = [self joins];
              NSArray *relationshipJoins = [relationship joins];
              int joinsCount = [joins count];
              int relationshipJoinsCount = [relationshipJoins count];

              EOFLOGObjectLevelArgs(@"EORelationship",
				    @"joinsCount=%d,relationshipJoinsCount=%d",
				    joinsCount, relationshipJoinsCount);

              if (joinsCount == relationshipJoinsCount)
                {
                  BOOL foundEachInverseJoin = YES;
                  int iJoin;

                  for (iJoin = 0;
		       foundEachInverseJoin && iJoin < joinsCount;
		       iJoin++)
                    {                  
                      EOJoin *join = [joins objectAtIndex: iJoin];
                      int iRelationshipJoin;
                      BOOL foundInverseJoin = NO;

                      EOFLOGObjectLevelArgs(@"EORelationship", @"%d join=%@",
					    iJoin, join);

                      for (iRelationshipJoin = 0;
			   !foundInverseJoin && iRelationshipJoin < joinsCount;
			   iRelationshipJoin++)
                        {
                          EOJoin *relationshipJoin =
			    [relationshipJoins objectAtIndex:iRelationshipJoin];

                          EOFLOGObjectLevelArgs(@"EORelationship",
						@"%d relationshipJoin=%@",
						iRelationshipJoin,
						relationshipJoin);

                          foundInverseJoin = [relationshipJoin
					       isReciprocalToJoin: join];

                          EOFLOGObjectLevelArgs(@"EORelationship",
						@"%d foundInverseJoin=%s",
						iRelationshipJoin,
						(foundInverseJoin ? "YES" : "NO"));
                        }

                      if (!foundInverseJoin)
                        foundEachInverseJoin = NO;

                      EOFLOGObjectLevelArgs(@"EORelationship",
					    @"%d foundEachInverseJoin=%s",
					    iJoin,
					    (foundEachInverseJoin ? "YES" : "NO"));
                    }

                  EOFLOGObjectLevelArgs(@"EORelationship",
					@"foundEachInverseJoin=%s",
					(foundEachInverseJoin ? "YES" : "NO"));

                  if (foundEachInverseJoin)
                    isReciprocal = YES;
                }
            }
        }
    }

  EOFLOGObjectFnStop();

  return isReciprocal;
}

/** "Search only already created inverse relationship in destination entity 
relationships. Nil if none" **/
- (EORelationship *)inverseRelationship
{
  //OK
  EOFLOGObjectFnStart();

  if (!_inverseRelationship)
    {
      EOEntity *destinationEntity;
      NSArray *destinationEntityRelationships;

      destinationEntity = [self destinationEntity];
      NSDebugLog(@"destinationEntity name=%@", [destinationEntity name]);

      destinationEntityRelationships = [destinationEntity relationships];

      NSDebugLog(@"destinationEntityRelationships=%@",
		 destinationEntityRelationships);

      if ([destinationEntityRelationships count] > 0)
        {
          int i, count = [destinationEntityRelationships count];

          for (i = 0; !_inverseRelationship && i < count; i++)
            {
              EORelationship *testRelationship =
		[destinationEntityRelationships objectAtIndex: i];

              NSDebugLog(@"testRelationship=%@", testRelationship);

              if ([self isReciprocalToRelationship: testRelationship])
                {
                  ASSIGN(_inverseRelationship, testRelationship);
                }
            }
        }

      NSDebugLog(@"_inverseRelationship=%@", _inverseRelationship);
    }

  EOFLOGObjectFnStop();

  return _inverseRelationship;
}

- (EORelationship *) _makeFlattenedInverseRelationship
{
  //OK
  EORelationship *inverseRelationship = nil;
  NSMutableString *invDefinition = nil;
  NSString *name = nil;
  int i, count;

  EOFLOGObjectFnStart();

  NSAssert([self isFlattened], @"Not Flatten Relationship");
  EOFLOGObjectLevel(@"EORelationship", @"add joins");

  count = [_definitionArray count];

  for (i = count - 1; i >= 0; i--)
    {
      EORelationship *rel = [_definitionArray objectAtIndex: i];
      EORelationship *invRel = [rel anyInverseRelationship];
      NSString *invRelName = [invRel name];

      if (invDefinition)
        {
          if (i < (count - 1))
            [invDefinition appendString: @"."];

	  [invDefinition appendString: invRelName];
        }
      else
        invDefinition = [NSMutableString stringWithString: invRelName];
    }

  inverseRelationship = [[EORelationship new] autorelease];
  [inverseRelationship setEntity: [self destinationEntity]];

  name = [NSString stringWithFormat: @"_eofInv_%@_%@",
		   [_entity name],
		   _name];
  [inverseRelationship setName: name]; 
  [inverseRelationship setDefinition: invDefinition]; 

  EOFLOGObjectLevel(@"EORelationship", @"add inverse rel");

  [(NSMutableArray*)[[self destinationEntity] _hiddenRelationships]
		    addObject: inverseRelationship]; //not very clean !!!
  EOFLOGObjectLevel(@"EORelationship", @"set inverse rel");

  [inverseRelationship _setInverseRelationship: self];

  EOFLOGObjectFnStop();

  return inverseRelationship;
}

- (EORelationship*) _makeInverseRelationship
{
  //OK
  EORelationship *inverseRelationship;
  NSString *name;
  NSArray *joins = nil;
  unsigned int i, count;

  EOFLOGObjectFnStart();

  NSAssert(![self isFlattened], @"Flatten Relationship");

  inverseRelationship = [[EORelationship new] autorelease];

  name = [NSString stringWithFormat: @"_eofInv_%@_%@",
		   [_entity name],
		   _name];
  [inverseRelationship setName: name]; 

  joins = [self joins];
  count = [joins count];

  EOFLOGObjectLevel(@"EORelationship", @"add joins");

  for (i = 0; i < count; i++)
    {
      EOJoin *join = [joins objectAtIndex: i];
      EOAttribute *sourceAttribute = [join sourceAttribute];
      EOAttribute *destinationAttribute = [join destinationAttribute];
      EOJoin *inverseJoin = [EOJoin joinWithSourceAttribute:
				      destinationAttribute //inverse souce<->destination attributes
				    destinationAttribute: sourceAttribute];

      [inverseRelationship addJoin: inverseJoin];
    }

  EOFLOGObjectLevel(@"EORelationship",@"add inverse rel");

  [(NSMutableArray*)[[self destinationEntity] _hiddenRelationships]
		    addObject: inverseRelationship]; //not very clean !!!

  EOFLOGObjectLevel(@"EORelationship", @"set inverse rel");

  [inverseRelationship _setInverseRelationship: self];

  /* call this last to avoid calls to [_destination _setIsEdited] */
  [inverseRelationship setEntity: _destination];
  EOFLOGObjectFnStop();

  return inverseRelationship;
}

- (EORelationship*) hiddenInverseRelationship
{
  //OK
  EOFLOGObjectFnStart();

  if (!_hiddenInverseRelationship)
    {
      if ([self isFlattened]) 
        _hiddenInverseRelationship = [self _makeFlattenedInverseRelationship];
      else
        _hiddenInverseRelationship = [self _makeInverseRelationship];
    }

  EOFLOGObjectFnStop();

  return _hiddenInverseRelationship;
}

- (EORelationship *)anyInverseRelationship
{
  //OK
  EORelationship *inverseRelationship = [self inverseRelationship];

  if (!inverseRelationship)
      inverseRelationship = [self hiddenInverseRelationship];

  return inverseRelationship;
}

- (unsigned int)numberOfToManyFaultsToBatchFetch
{
  return _batchCount;
}

- (BOOL)ownsDestination
{
  return _flags.ownsDestination;
}

- (EOQualifier *)qualifierWithSourceRow: (NSDictionary *)sourceRow
{
  [self notImplemented: _cmd];//TODO
  return nil;
}

@end /* EORelationship */


@implementation EORelationship (EORelationshipEditing)

- (NSException *)validateName: (NSString *)name
{
  //Seems OK
  const char *p, *s = [name cString];
  int exc = 0;
  NSArray *storedProcedures = nil;

  if ([_name isEqual:name]) return nil;
  if (!name || ![name length])
    exc++;
  if (!exc)
    {
      p = s;
      while (*p)
        {
          if(!isalnum(*p) &&
             *p != '@' && *p != '#' && *p != '_' && *p != '$')
            {
              exc++;
              break;
            }
          p++;
        }
      if (!exc && *s == '$')
        exc++;
  
    if (exc)
      return [NSException exceptionWithName: NSInvalidArgumentException
                         reason: [NSString stringWithFormat: @"%@ -- %@ 0x%x: argument \"%@\" contains invalid char '%c'", 
					  NSStringFromSelector(_cmd),
					  NSStringFromClass([self class]),
					  self,
                                         name,
					 *p]
                        userInfo: nil];
      
      if ([[self entity] _hasAttributeNamed: name])
        exc++;
      else if ([[self entity] anyRelationshipNamed: name])
        exc++;
      else if ((storedProcedures = [[[self entity] model] storedProcedures]))
        {
          NSEnumerator *stEnum = [storedProcedures objectEnumerator];
          EOStoredProcedure *st;
          
          while ((st = [stEnum nextObject]))
            {
              NSEnumerator *attrEnum;
              EOAttribute  *attr;
              
              attrEnum = [[st arguments] objectEnumerator];
              while ((attr = [attrEnum nextObject]))
                {
                  if ([name isEqualToString: [attr name]])
                    {
                      exc++;
                      break;
                    }
                }
                if (exc)
                  break;
            }
        }
    }

  if (exc)
    {
      return [NSException exceptionWithName: NSInvalidArgumentException
                         reason: [NSString stringWithFormat: @"%@ -- %@ 0x%x: \"%@\" already used in the model",
                                 NSStringFromSelector(_cmd),
                                 NSStringFromClass([self class]),
                                 self,
                                 name]
                        userInfo: nil];
    }

  return nil;
}

- (void)setToMany: (BOOL)flag
{
  //OK
  if ([self isFlattened])
    [NSException raise: NSInvalidArgumentException
                 format: @"%@ -- %@ 0x%x: receiver is a flattened relationship",
                 NSStringFromSelector(_cmd),
                 NSStringFromClass([self class]),
                 self];

  if (_flags.isToMany != flag)
    {
      [self willChange];
      [_entity _setIsEdited];
      _flags.isToMany = flag;
    }
}

- (void)setName: (NSString *)name
{
  //OK
  [[self validateName: name] raise];
  [self willChange];
  [_entity _setIsEdited];

  ASSIGNCOPY(_name, name);
}

- (void)setDefinition: (NSString *)definition
{
  //Near OK
  EOFLOGObjectFnStart();

  EOFLOGObjectLevelArgs(@"EORelationship", @"definition=%@", definition);

  [self _flushCache];
  [self willChange];

  if (definition)
    {
      _flags.isToMany = NO;

      NSAssert1(_entity,@"No entity for relationship %@",
                self);

      ASSIGN(_definitionArray, [_entity _parseRelationshipPath: definition]);

      EOFLOGObjectLevelArgs(@"EORelationship", @"_definitionArray=%@", _definitionArray);
      EOFLOGObjectLevelArgs(@"EORelationship", @"[self definition]=%@", [self definition]);

      _destination = nil;

      {        
        //TODO VERIFY
        //TODO better ?
        int i, count = [_definitionArray count];

        EOFLOGObjectLevelArgs(@"EORelationship", @"==> _definitionArray=%@",
			      _definitionArray);

        for (i = 0; !_flags.isToMany && i < count; i++)
          {
            EORelationship *rel = [_definitionArray objectAtIndex: i];

            if ([rel isKindOfClass: [EORelationship class]])
              {
                if ([rel isToMany])
                  _flags.isToMany = YES;
            }
          else
            break;
        }
      }

    }
  else /* definition == nil */
    {
      DESTROY(_definitionArray);
    }
  /* Ayers: Not sure what justifies this. */
  [_entity _setIsEdited];

  EOFLOGObjectFnStop();
}

/**
 * <p>Sets the entity of the reciever.</p>
 * <p>If the receiver already has an entity assigned to it the old relationship
 * will will be removed first.</p>
 * <p>This method is used by [EOEntity-addRelationship:] and
 * [EOEntity-removeRelationship:] which should be used for general relationship
 * manipulations.  This method should only be useful
 * when creating flattend relationships programmatically.</p>
 */
- (void)setEntity: (EOEntity *)entity
{
  //OK
  if (entity != _entity)
    {
      [self _flushCache];
      [self willChange];

      if (_entity)
	{
	  NSString *relationshipName;
	  EORelationship *relationship;

	  /* Check if we are still in the entities arrays to
	     avoid recursive loop when removeRelationship:
	     calls this method.  */
	  relationshipName = [self name];
	  relationship = [_entity relationshipNamed: relationshipName];
          if (self == relationship)
	    {
	      [_entity removeRelationship: self];
	    }
	}
      _entity = entity;
    }
  /* This method is used by EOEntity's remove/addRelatinship: and is not
     responsible for calling _setIsEdited on the entity.  */
}

- (void)setUserInfo: (NSDictionary *)dictionary
{
  //OK
  [self willChange];
  ASSIGN(_userInfo, dictionary);
  /* Ayers: Not sure what justifies this. */
  [_entity _setIsEdited];
}

- (void)setInternalInfo: (NSDictionary *)dictionary
{
  //OK
  [self willChange];
  ASSIGN(_internalInfo, dictionary);
  /* Ayers: Not sure what justifies this. */
  [_entity _setIsEdited];
}

- (void)setDocComment: (NSString *)docComment
{
  //OK
  [self willChange];
  ASSIGNCOPY(_docComment, docComment);
  /* Ayers: Not sure what justifies this. */
  [_entity _setIsEdited];
}

- (void)setPropagatesPrimaryKey: (BOOL)flag
{
  //OK
  if (_flags.propagatesPrimaryKey != flag)
    [self willChange];

  _flags.propagatesPrimaryKey = flag;
}

- (void)setIsBidirectional: (BOOL)flag
{
  //OK
  if (_flags.isBidirectional != flag)
    [self willChange];

  _flags.isBidirectional = flag;
}

- (void)setOwnsDestination: (BOOL)flag
{
  if (_flags.ownsDestination != flag)
    [self willChange];

  _flags.ownsDestination = flag;
}

- (void)addJoin: (EOJoin *)join
{
  EOAttribute *sourceAttribute = nil;
  EOAttribute *destinationAttribute = nil;

  EOFLOGObjectFnStart();

  EOFLOGObjectLevelArgs(@"EORelationship", @"Add join: %@\nto %@", join, self);

  if ([self isFlattened] == YES)
    [NSException raise: NSInvalidArgumentException
                 format: @"%@ -- %@ 0x%x: receiver is a flattened relationship",
                 NSStringFromSelector(_cmd),
                 NSStringFromClass([self class]),
                 self];
  else
    {
      EOEntity *destinationEntity = [self destinationEntity];
      EOEntity *sourceEntity = [self entity];

      EOFLOGObjectLevelArgs(@"EORelationship", @"destinationEntity=%@", destinationEntity);

      if (!destinationEntity)
        {
          NSEmitTODO(); //TODO
          EOFLOGObjectLevelArgs(@"EORelationship", @"self=%@", self);
          //TODO ??
        };

      sourceAttribute = [join sourceAttribute];

      NSAssert3(sourceAttribute, @"No source attribute in join %@ in relationship %@ of entity %@",
                join,
                self,
                sourceEntity);

      destinationAttribute = [join destinationAttribute];

      NSAssert3(destinationAttribute, @"No destination attribute in join %@ in relationship %@ of entity %@",                
                join,
                self,
                sourceEntity);

      if ([sourceAttribute isFlattened] == YES
	  || [destinationAttribute isFlattened] == YES)
        [NSException raise: NSInvalidArgumentException
                     format: @"%@ -- %@ 0x%x: join's attributes are flattened",
                     NSStringFromSelector(_cmd),
                     NSStringFromClass([self class]),
                     self];
      else
        {
          EOEntity *joinDestinationEntity = [destinationAttribute entity];
          EOEntity *joinSourceEntity = [sourceAttribute entity];

/*          if (destinationEntity && ![[destinationEntity name] isEqual:[joinSourceEntity name]])
            {
              [NSException raise:NSInvalidArgumentException
                           format:@"%@ -- %@ 0x%x: join source entity (%@) is not equal to last join entity (%@)",
                           NSStringFromSelector(_cmd),
                           NSStringFromClass([self class]),
                           self,
                           [joinSourceEntity name],
                           [destinationEntity name]];
            }*/

          if (sourceEntity
	      && ![[joinSourceEntity name] isEqual: [sourceEntity name]])
              [NSException raise: NSInvalidArgumentException
                           format: @"%@ -- %@ 0x%x (%@): join source entity (%@) is not equal to relationship entity (%@)",
                           NSStringFromSelector(_cmd),
                           NSStringFromClass([self class]),
                           self,
                           [self name],
                           [joinSourceEntity name],
                           [sourceEntity name]];
          else if (destinationEntity
		   && ![[joinDestinationEntity name]
			 isEqual: [destinationEntity name]])
              [NSException raise: NSInvalidArgumentException
                           format: @"%@ -- %@ 0x%x (%@): join destination entity (%@) is not equal to relationship destination entity (%@)",
                           NSStringFromSelector(_cmd),
                           NSStringFromClass([self class]),
                           self,
                           [self name],
                           [joinDestinationEntity name],
                           [destinationEntity name]];
          else
            {
              if ([_sourceAttributes count])
                {
                  EOAttribute *sourceAttribute = [join sourceAttribute];
                  EOAttribute *destinationAttribute;

		  destinationAttribute = [join destinationAttribute];

                  if (([_sourceAttributes indexOfObject: sourceAttribute]
                      != NSNotFound)
		      && ([_destinationAttributes
			    indexOfObject: destinationAttribute]
			  != NSNotFound))
                    [NSException raise: NSInvalidArgumentException
                                 format: @"%@ -- %@ 0x%x: TODO",
                                 NSStringFromSelector(_cmd),
                                 NSStringFromClass([self class]),
                                 self];
                }

                [self _flushCache];
                [self willChange];

                EOFLOGObjectLevel(@"EORelationship", @"really add");
                EOFLOGObjectLevelArgs(@"EORelationship", @"XXjoins %p class%@",
				      _joins, [_joins class]);

                if (!_joins)
                  _joins = [NSMutableArray new];

                [(NSMutableArray *)_joins addObject: join];      

                EOFLOGObjectLevelArgs(@"EORelationship", @"XXjoins %p class%@",
					  _joins, [_joins class]);

                EOFLOGObjectLevel(@"EORelationship", @"added");

                [self _joinsChanged];
		/* Ayers: Not sure what justifies this. */
		[_entity _setIsEdited];
            }
        }
    }

  EOFLOGObjectFnStop();
}

- (void)removeJoin: (EOJoin *)join
{
  EOFLOGObjectFnStart();

  [self _flushCache];

  if ([self isFlattened] == YES)
    [NSException raise: NSInvalidArgumentException
                 format: @"%@ -- %@ 0x%x: receiver is a flattened relationship",
                 NSStringFromSelector(_cmd),
                 NSStringFromClass([self class]),
                 self];
  else
    {
      [self willChange];
      [(NSMutableArray *)_joins removeObject: join];

          /*NO: will be recomputed      [(NSMutableArray *)_sourceAttributes
            removeObject:[join sourceAttribute]];
            [(NSMutableArray *)_destinationAttributes
            removeObject:[join destinationAttribute]];
          */

      EOFLOGObjectLevelArgs(@"EORelationship", @"XXjoins %p class%@",
		       _joins, [_joins class]);

      [self _joinsChanged];
      /* Ayers: Not sure what justifies this. */
      [_entity _setIsEdited];
    }

  EOFLOGObjectFnStop();
}

- (void)setJoinSemantic: (EOJoinSemantic)joinSemantic
{
  //OK
  [self willChange];
  _joinSemantic = joinSemantic;
}

- (void)beautifyName
{
  /*+ Make the name conform to the Next naming style
    NAME -> name, FIRST_NAME -> firstName +*/
  NSArray  *listItems;
  NSString *newString = [NSString string];
  int	    anz, i;

  EOFLOGObjectFnStartOrCond2(@"ModelingClasses", @"EORelationship");
  
  /* 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:
			       [[listItems objectAtIndex: 0] lowercaseString]];
      anz = [listItems count];

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

    // Exception abfangen
    NS_DURING
      {
        [self setName:newString];
      }
    NS_HANDLER
      {
        NSLog(@"%@ in Class: EORlationship , Method: beautifyName >> error : %@",
	      [localException name], [localException reason]);
      }
    NS_ENDHANDLER;
  }
  
  EOFLOGObjectFnStopOrCond2(@"ModelingClasses", @"EORelationship");
}

- (void)setNumberOfToManyFaultsToBatchFetch: (unsigned int)size
{
  [self willChange];
  _batchCount = size;
}

- (void)setDeleteRule: (EODeleteRule)deleteRule
{
  NSAssert1(deleteRule >= 0 && deleteRule < 4,
	    @"Bad deleteRule numeric value: %d",
            deleteRule);

  [self willChange];
  _flags.deleteRule = deleteRule;
}

- (void)setIsMandatory: (BOOL)isMandatory
{
  //OK
  [self willChange];
  _flags.isMandatory = isMandatory;
}

@end

@implementation EORelationship (EORelationshipValueMapping)

/**
 * If the reciever is a manditory relationship, this method
 * returns an exception if the value pointed to by VALUEP is
 * either nil or the EONull instance for to-one relationships
 * or an empty NSArray for to-many relationships.  Otherwise
 * it returns nil.  EOClassDescription adds further information
 * to this exception before it gets passed to the application or
 * user.
 */
- (NSException *)validateValue: (id*)valueP
{
  //OK
  NSException *exception = nil;

  EOFLOGObjectFnStart();

  NSAssert(valueP, @"No value pointer");

  if ([self isMandatory])
    {
      BOOL isToMany = [self isToMany];

      if ((isToMany == NO && _isNilOrEONull(*valueP))
	  || (isToMany == YES && [*valueP count] == 0))
        {
          EOEntity *destinationEntity = [self destinationEntity];
          EOEntity *entity = [self entity];

          exception = [NSException validationExceptionWithFormat:
				     @"The %@ property of %@ must have a %@ assigned",
				   [self name],
				   [entity name],
				   [destinationEntity name]];
        }
    }

  EOFLOGObjectFnStop();

  return exception;
}

@end

@implementation EORelationship (EORelationshipPrivate)

/*
  This method is private to GDL2 to allow the inverse relationship
  to be set from the original relationship.  It exists to avoid the
  ASSIGN(inverseRelationship->_inverseRelationship, self);
  and to insure that associations will be updated if we ever display
  inverse relationships in DBModeler.
*/
- (void)_setInverseRelationship: (EORelationship*)relationship
{
  [self willChange];
  ASSIGN(_inverseRelationship,relationship);
}

@end

@implementation EORelationship (EORelationshipXX)

- (NSArray*) _intermediateAttributes
{
  //Verify !!
  NSMutableArray *intermediateAttributes;
  EORelationship *rel;
  NSArray *joins;

  //all this works on flattened and non flattened relationship.
  intermediateAttributes = [NSMutableArray array];
  rel = [self firstRelationship];
  joins = [rel joins];
  //??
  [intermediateAttributes addObjectsFromArray:
			    [joins resultsOfPerformingSelector:
				     @selector(destinationAttribute)]];

  rel = [self lastRelationship];
  joins = [rel joins];
  //  attribute = [joins sourceAttribute];
  //??
  [intermediateAttributes addObjectsFromArray:
			    [joins resultsOfPerformingSelector:
				     @selector(sourceAttribute)]];

  return [NSArray arrayWithArray: intermediateAttributes];
}

/** Return the last relationship if self is flattened, self otherwise.
**/
- (EORelationship*) lastRelationship
{
  EORelationship *lastRel;

  if ([self isFlattened])
    {
      NSAssert(!_definitionArray || [_definitionArray count] > 0,
               @"Definition array is empty");

      lastRel = [[self _definitionArray] lastObject];
    }
  else
    lastRel = self;

  return lastRel;
}

/** Return the 1st relationship if self is flattened, self otherwise.
**/
- (EORelationship*) firstRelationship
{
  EORelationship *firstRel;

  if ([self isFlattened])
    {
      NSAssert(!_definitionArray || [_definitionArray count] > 0,
               @"Definition array is empty");

      firstRel = [[self _definitionArray] objectAtIndex: 0];
    }
  else
    firstRel = self;

  return firstRel;
}

- (EOEntity*) intermediateEntity
{
  //TODO verify
  id intermediateEntity = nil;

  if ([self isToManyToOne])
    {
      int i, count = [_definitionArray count];

      for (i = (count - 1); !intermediateEntity && i >= 0; i--)
        {
          EORelationship *rel = [_definitionArray objectAtIndex: i];

          if ([rel isToMany])
            intermediateEntity = [rel destinationEntity];
        }
    }

  return intermediateEntity;
}

- (BOOL) isMultiHop
{
  //TODO verify
  BOOL isMultiHop = NO;

  if ([self isFlattened])
    {
      isMultiHop = YES;
    }

  return isMultiHop;
}

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

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

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

/** Return relationship path (like toRel1.toRel2) if self is flattened, slef name otherwise.
**/
- (NSString*)relationshipPath
{
  //Seems OK
  NSString *relationshipPath = nil;

  EOFLOGObjectFnStart();

  if ([self isFlattened])
    {
      int i, count = [_definitionArray count];

      for (i = 0; i < count; i++)
        {
          EORelationship *relationship = [_definitionArray objectAtIndex: i];
          NSString *relationshipName = [relationship name];

          if (relationshipPath)
            [(NSMutableString*)relationshipPath appendString: @"."];
          else
            relationshipPath = [NSMutableString string];

          [(NSMutableString*)relationshipPath appendString: relationshipName];
        }
    }
  else
    relationshipPath = [self name];

  EOFLOGObjectFnStop();

  return relationshipPath;
}

-(BOOL)isToManyToOne
{
  BOOL isToManyToOne = NO;

  EOFLOGObjectFnStart();

  if ([self isFlattened])
    {
      BOOL isToMany = YES;
      int count = [_definitionArray count];

      if (count >= 2)
        {
          EORelationship *firstRelationship = [_definitionArray
						objectAtIndex: 0];

          isToMany = [firstRelationship isToMany];

          if (!isToMany)
            {
              if ([firstRelationship isParentRelationship])
                {
                  NSEmitTODO();  //TODO
                  EOFLOGObjectLevelArgs(@"EORelationship", @"self=%@", self);
                  EOFLOGObjectLevelArgs(@"EORelationship", @"firstRelationship=%@",
			       firstRelationship);

                  [self notImplemented: _cmd]; //TODO
                }
            }

          if (isToMany)
            {
              EORelationship *secondRelationship = [_definitionArray
						     objectAtIndex: 0];

              if (![secondRelationship isToMany])
                {
                  EORelationship *invRel = [secondRelationship
					     anyInverseRelationship];

                  if (invRel)
                    secondRelationship = invRel;

                  isToManyToOne = YES;

                  if ([secondRelationship isParentRelationship])
                    {
                      NSEmitTODO();  //TODO
                      EOFLOGObjectLevelArgs(@"EORelationship", @"self=%@", self);
                      EOFLOGObjectLevelArgs(@"EORelationship", @"secondRelationship=%@",
				   secondRelationship);

                      [self notImplemented: _cmd]; //TODO
                    }
                }
            }
        }
    }

  EOFLOGObjectFnStop();

  return isToManyToOne;
}

-(NSDictionary*)_sourceToDestinationKeyMap
{
  //OK
  EOFLOGObjectFnStart();

  if (!_sourceToDestinationKeyMap)
    {
      NSString *relationshipPath = [self relationshipPath];

      ASSIGN(_sourceToDestinationKeyMap,
	     [_entity _keyMapForRelationshipPath: relationshipPath]);
    }

  EOFLOGObjectFnStop();

  return _sourceToDestinationKeyMap;
}

- (BOOL)foreignKeyInDestination
{
  NSArray *destAttributes = nil;
  NSArray *primaryKeyAttributes = nil;
  int destAttributesCount = 0;
  int primaryKeyAttributesCount = 0;
  BOOL foreignKeyInDestination = NO;

  EOFLOGObjectFnStart();

  destAttributes = [self destinationAttributes];
  primaryKeyAttributes = [[self destinationEntity] primaryKeyAttributes];

  destAttributesCount = [destAttributes count];
  primaryKeyAttributesCount = [primaryKeyAttributes count];

  EOFLOGObjectLevelArgs(@"EORelationship", @"destAttributes=%@",
			destAttributes);
  EOFLOGObjectLevelArgs(@"EORelationship", @"primaryKeyAttributes=%@",
			primaryKeyAttributes);

  if (destAttributesCount > 0 && primaryKeyAttributesCount > 0)
    {
      int i;

      for (i = 0;
	   !foreignKeyInDestination && i < destAttributesCount;
	   i++)
	{
	  EOAttribute *attribute = [destAttributes objectAtIndex: i];
	  int pkAttrIndex = [primaryKeyAttributes
			      indexOfObjectIdenticalTo: attribute];

	  foreignKeyInDestination = (pkAttrIndex == NSNotFound);
	}
    }

  EOFLOGObjectFnStop();

  EOFLOGObjectLevelArgs(@"EORelationship", @"foreignKeyInDestination=%s",
			(foreignKeyInDestination ? "YES" : "NO"));

  return foreignKeyInDestination;
}

@end

@implementation EORelationship (EORelationshipPrivate2)

- (BOOL) isPropagatesPrimaryKeyPossible
{
/*
  NSArray* joins=[self joins];
  NSArray* joinsSourceAttributes=[joins resultsOfPerformingSelector:@selector(sourceAttribute)];
  NSArray* joinsDestinationAttributes=[joins resultsOfPerformingSelector:@selector(destinationAttribute)];

joinsSourceAttributes names
sortedArrayUsingSelector:compare:

result count

joinsDestinationAttributes names
sortedArrayUsingSelector:compare:
inverseRelationship
inv entity [EOEntity]:
inv ventity primaryKeyAttributeNames
count
dest entity
dst entity primaryKeyAttributeNames 

*/
  EOFLOGObjectFnStart();

  [self notImplemented: _cmd]; // TODO

  EOFLOGObjectFnStop();

  return NO;
};

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

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

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

/** Return dictionary of key/value for destination object of source row/object **/
- (EOMutableKnownKeyDictionary *) _foreignKeyForSourceRow: (NSDictionary*)row
{
  EOMutableKnownKeyDictionary *foreignKey = nil;
  EOMKKDSubsetMapping *sourceRowToForeignKeyMapping = nil;

  EOFLOGObjectFnStart();

  sourceRowToForeignKeyMapping = [self _sourceRowToForeignKeyMapping];

  EOFLOGObjectLevelArgs(@"EORelationship", @"self=%@",self);
  EOFLOGObjectLevelArgs(@"EORelationship", @"sourceRowToForeignKeyMapping=%@",
	       sourceRowToForeignKeyMapping);

  foreignKey = [EOMutableKnownKeyDictionary dictionaryFromDictionary: row
					    subsetMapping:
					      sourceRowToForeignKeyMapping];

  EOFLOGObjectLevelArgs(@"EORelationship", @"row=%@\nforeignKey=%@", row, foreignKey);

  EOFLOGObjectFnStop();

  return foreignKey;
}

- (EOMKKDSubsetMapping*) _sourceRowToForeignKeyMapping
{
  EOFLOGObjectFnStart();

  if (!_sourceRowToForeignKeyMapping)
    {
      NSDictionary *sourceToDestinationKeyMap;
      NSArray *sourceKeys;
      NSArray *destinationKeys;
      EOEntity *destinationEntity;
      EOMKKDInitializer *destinationDictionaryInitializer = nil;
      EOMKKDInitializer *adaptorDictionaryInitializer;
      EOMKKDSubsetMapping *sourceRowToForeignKeyMapping;

      sourceToDestinationKeyMap = [self _sourceToDestinationKeyMap];

      EOFLOGObjectLevelArgs(@"EORelationship", @"rel=%@ sourceToDestinationKeyMap=%@",
		   [self name], sourceToDestinationKeyMap);

      sourceKeys = [sourceToDestinationKeyMap objectForKey: @"sourceKeys"];
      EOFLOGObjectLevelArgs(@"EORelationship", @"rel=%@ sourceKeys=%@",
                            [self name], sourceKeys);

      destinationKeys = [sourceToDestinationKeyMap
			  objectForKey: @"destinationKeys"];
      EOFLOGObjectLevelArgs(@"EORelationship", @"rel=%@ destinationKeys=%@",
                            [self name], destinationKeys);

      destinationEntity = [self destinationEntity];

      destinationDictionaryInitializer = [destinationEntity _adaptorDictionaryInitializer];

      EOFLOGObjectLevelArgs(@"EORelationship", @"destinationEntity named %@  primaryKeyDictionaryInitializer=%@",
		   [destinationEntity name],
		   destinationDictionaryInitializer);

      adaptorDictionaryInitializer = [_entity _adaptorDictionaryInitializer];
      EOFLOGObjectLevelArgs(@"EORelationship",@"entity named %@ adaptorDictionaryInitializer=%@",
                  [_entity name],
                  adaptorDictionaryInitializer);

      sourceRowToForeignKeyMapping = 
	[destinationDictionaryInitializer
	  subsetMappingForSourceDictionaryInitializer:
	    adaptorDictionaryInitializer
	  sourceKeys: sourceKeys
	  destinationKeys: destinationKeys];

      ASSIGN(_sourceRowToForeignKeyMapping, sourceRowToForeignKeyMapping);

      EOFLOGObjectLevelArgs(@"EORelationship",@"%@ to %@: _sourceRowToForeignKeyMapping=%@",
		   [_entity name],
		   [destinationEntity name],
		   _sourceRowToForeignKeyMapping);
    }

  EOFLOGObjectFnStop();

  return _sourceRowToForeignKeyMapping;
}

- (NSArray*) _sourceAttributeNames
{
  //Seems OK
  return [[self sourceAttributes]
	   resultsOfPerformingSelector: @selector(name)];
}

- (EOJoin*) joinForAttribute: (EOAttribute*)attribute
{
  //OK
  EOJoin *join = nil;
  int i, count = [_joins count];

  for (i = 0; !join && i < count; i++)
    {
      EOJoin *aJoin = [_joins objectAtIndex: i];
      EOAttribute *sourceAttribute = [aJoin sourceAttribute];

      if ([attribute isEqual: sourceAttribute])
        join = aJoin;
    }

  return join;
}

- (void) _flushCache
{
  //VERIFY
  //[self notImplemented:_cmd]; // TODO
  DESTROY(_sourceAttributes);
  DESTROY(_destinationAttributes);
  DESTROY(_inverseRelationship);
  DESTROY(_hiddenInverseRelationship);
  DESTROY(_componentRelationships);
  _destination = nil;

}

- (EOExpressionArray*) _definitionArray
{
  //VERIFY
  return _definitionArray;
}

- (NSString*) _stringFromDeleteRule: (EODeleteRule)deleteRule
{
  NSString *deleteRuleString = nil;

  switch(deleteRule)
    {
    case EODeleteRuleNullify:
      deleteRuleString = @"";
      break;
    case EODeleteRuleCascade:
      deleteRuleString = @"";
      break;
    case EODeleteRuleDeny:
      deleteRuleString = @"";
      break;
    case EODeleteRuleNoAction:
      deleteRuleString = @"";
      break;
    default:
      [NSException raise: NSInvalidArgumentException
                   format: @"%@ -- %@ 0x%x: invalid deleteRule code for relationship '%@': %d", 
                   NSStringFromSelector(_cmd), 
                   NSStringFromClass([self class]), 
                   self, 
                   [self name], 
                   (int)deleteRule];
      break;
    }

  return deleteRuleString;
}

- (EODeleteRule) _deleteRuleFromString: (NSString*)deleteRuleString
{
  EODeleteRule deleteRule = 0;

  if ([deleteRuleString isEqualToString: @"EODeleteRuleNullify"])
    deleteRule = EODeleteRuleNullify;
  else if ([deleteRuleString isEqualToString: @"EODeleteRuleCascade"])
    deleteRule = EODeleteRuleCascade;
  else if ([deleteRuleString isEqualToString: @"EODeleteRuleDeny"])
    deleteRule = EODeleteRuleDeny;
  else if ([deleteRuleString isEqualToString: @"EODeleteRuleNoAction"])
    deleteRule = EODeleteRuleNoAction;
  else 
    [NSException raise: NSInvalidArgumentException
                 format: @"%@ -- %@ 0x%x: invalid deleteRule string for relationship '%@': %@", 
                 NSStringFromSelector(_cmd), 
                 NSStringFromClass([self class]), 
                 self, 
                 [self name], 
                 deleteRuleString];

  return deleteRule;
}

- (NSDictionary*) _rightSideKeyMap
{
  NSDictionary *keyMap = nil;

  NSEmitTODO();  //TODO

  [self notImplemented: _cmd]; // TODO

  if ([self isToManyToOne])
    {
      int count = [_definitionArray count];

      if (count >= 2) //??
        {
          EORelationship *rel0 = [_definitionArray objectAtIndex: 0];

          if ([rel0 isToMany]) //??
            {
              EOEntity *entity = [rel0 destinationEntity];
              EORelationship *rel1 = [_definitionArray objectAtIndex: 1];

              keyMap = [entity _keyMapForIdenticalKeyRelationshipPath:
				 [rel1 name]];
            }
        }
    }

  return keyMap;
}

- (NSDictionary *) _leftSideKeyMap
{
  NSDictionary *keyMap = nil;

  NSEmitTODO();  //TODO

  [self notImplemented: _cmd]; // TODO

  if ([self isToManyToOne])
    {
      int count = [_definitionArray count];

      if (count >= 2) //??
        {
          EORelationship *rel = [_definitionArray objectAtIndex: 0];

          if ([rel isToMany]) //??
            {
              EOEntity *entity = [rel entity];

              keyMap = [entity _keyMapForIdenticalKeyRelationshipPath:
				 [rel name]];
            }
        }
    }

  return keyMap;
}

- (EORelationship*)_substitutionRelationshipForRow: (NSDictionary*)row
{
  EOEntity *entity = [self entity];
  EOModel *model = [entity model];
  EOModelGroup *modelGroup = [model modelGroup];

  if (modelGroup)
    {
      //??
      //NSEmitTODO();  //TODO
    }

  return self;
}

- (void) _joinsChanged
{
  //VERIFIED DA 
  int count = [_joins count];

  EOFLOGObjectFnStart();


  EOFLOGObjectLevelArgs(@"EORelationship", @"_joinsChanged:%@\nin %@", _joins, self);

  if (count > 0)
    {
      EOJoin *join = [_joins objectAtIndex: 0];
      EOAttribute *destinationAttribute = [join destinationAttribute];
      EOEntity *destinationEntity = [destinationAttribute entity];

      _destination = destinationEntity;
    }
  else
    {
      _destination = nil;
    }

  EOFLOGObjectFnStop();
}

@end
