//
// $Id$
//
// MCPRelation.m
// MCPKit
//
// Created by Serge Cohen (serge.cohen@m4x.org) on 11/08/04.
// Copyright (c) 2004 Serge Cohen. All rights reserved.
//
// Forked by the Sequel Pro team (sequelpro.com), April 2009
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// More info at
// More info at
#import "MCPRelation.h"
#import "MCPRelation+Private.h"
#import "MCPEntrepriseNotifications.h"
#import "MCPModel.h"
#import "MCPClassDescription.h"
#import "MCPClassDescription+Private.h"
#import "MCPAttribute.h"
#import "MCPJoin.h"
static NSArray *MCPexistingDeleteRules;
@implementation MCPRelation
#pragma mark Class methods
+ (void) initialize
{
if (self = [MCPRelation class]) {
NSMutableArray *theExistingDeleteRules = [[NSMutableArray alloc] init];
[self setVersion:010101]; // Ma.Mi.Re -> MaMiRe
[theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInteger:OnDeleteNullify], @"tag", @"Nullify", @"name", nil]];
[theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInteger:OnDeleteDeny], @"tag", @"Deny", @"name", nil]];
[theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInteger:OnDeleteCascade], @"tag", @"Cascade", @"name", nil]];
[theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInteger:OnDeleteDefault], @"tag", @"Default", @"name", nil]];
[theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInteger:OnDeleteNoAction], @"tag", @"No Action", @"name", nil]];
MCPexistingDeleteRules = [[NSArray alloc] initWithArray:theExistingDeleteRules];
[theExistingDeleteRules release];
}
return;
}
+ (NSArray *) existingDeleteRules
{
return MCPexistingDeleteRules;
}
- (NSArray *) existingDeleteRules
{
return [MCPRelation existingDeleteRules];
}
#pragma mark Life cycle
- (id) initWithName:(NSString *) iName from:(MCPClassDescription *) iFrom to:(MCPClassDescription *) iTo
{
self = [super init];
if (self) {
[self setName:iName];
[self setOrigin:iFrom];
[self setDestination:iTo];
joins = [[NSMutableArray alloc] init];
}
return self;
}
- (void) invalidateRelation
{
[self retain]; // To be sure not to be released before the end of the method
[self invalidateJoins]; // Remove each of the joins (so that attributes get notified)
/*
[origin removeObjectFromRelationsAtIndex:[[origin relations] indexOfObjectIdenticalTo:self]];
origin = nil;
[destination removeObjectFromIncomingsAtIndex:[[destination incomings] indexOfObjectIdenticalTo:self]];
destination = nil;
*/
[self setOrigin:nil];
[self setDestination:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
[self release];
}
- (void) dealloc
{
[joins release]; // Should be empty by now...
[name release];
// The inverse relation don't have an inverse relation any more...
// [inverseRelation setInverseRelation:nil];
[self setInverseRelation:nil];
// Other are weak references.
[super dealloc];
}
#pragma mark NSCoding protocol
- (id) initWithCoder:(NSCoder *) decoder
{
self = [super init];
if ((self) && ([decoder allowsKeyedCoding])) {
[self setName:[decoder decodeObjectForKey:@"MCPname"]];
[self setDeleteRule:(MCPRelationDeleteRule)[decoder decodeInt32ForKey:@"MCPdeleteRule"]];
if ([decoder containsValueForKey:@"MCPinverseRelation"]) {
[self setInverseRelation:[decoder decodeObjectForKey:@"MCPinverseRelation"]];
}
else {
[self setInverseRelation:nil];
}
[self setOrigin:[decoder decodeObjectForKey:@"MCPorigin"]];
[self setDestination:[decoder decodeObjectForKey:@"MCPdestination"]];
// [self setJoins:[decoder decodeObjectForKey:@"MCPjoins"]];
joins = [[NSMutableArray alloc] initWithArray:[decoder decodeObjectForKey:@"MCPjoins"]];
[self setIsToMany:[decoder decodeBoolForKey:@"MCPisToMany"]];
[self setIsMandatory:[decoder decodeBoolForKey:@"MCPisMandatory"]];
[self setOwnsDestintation:[decoder decodeBoolForKey:@"MCPownsDestination"]];
}
return self;
}
- (void) encodeWithCoder:(NSCoder *) encoder
{
if (! [encoder allowsKeyedCoding]) {
NSLog(@"In MCPRelation -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!");
return;
}
[encoder encodeObject:[self name] forKey:@"MCPname"];
[encoder encodeInt32:(int32_t)[self deleteRule] forKey:@"MCPdeleteRule"];
if ([self inverseRelation]) {
[encoder encodeObject:[self inverseRelation] forKey:@"MCPinverseRelation"];
}
[encoder encodeObject:[self origin] forKey:@"MCPorigin"];
[encoder encodeObject:[self destination] forKey:@"MCPdestination"];
[encoder encodeObject:[self joins] forKey:@"MCPjoins"];
[encoder encodeBool:[self isToMany] forKey:@"MCPisToMany"];
[encoder encodeBool:[self isMandatory] forKey:@"MCPisMandatory"];
[encoder encodeBool:[self ownsDestination] forKey:@"MCPownsDestination"];
}
#pragma mark Making new joins
/*
- (MCPJoin *) addNewJoin // Usefull for the interface, to be able to create a new join by just using a binding.
{
[self addJoinFrom:[origin objectInAttributesAtIndex:0] to:[destination objectInAttributesAtIndex:0]];
}
*/
- (MCPJoin *) addJoinFrom:(MCPAttribute *) iFrom to:(MCPAttribute *) iTo
{
MCPJoin *theJoin;
if ([iFrom classDescription] != [self origin]) {
NSLog(@"Tried to make a join starting from an attribute (%@) that does NOT belong to the origin class description (%@)! Will not perform the link", iFrom, [self origin]);
return nil;
}
if ([iTo classDescription] != [self destination]) {
NSLog(@"Tried to make a join arriving to an attribute (%@) that does NOT belong to the destination class description (%@)! Will not perform the link", iTo, [self destination]);
return nil;
}
theJoin = [[MCPJoin alloc] initForRelation:self from:iFrom to:iTo];
// theJoin = [[MCPJoin alloc] initFrom:iFrom to:iTo];
// [joins addObject:theJoin];
[joins insertObject:theJoin atIndex:[joins count]];
[theJoin release];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
return theJoin;
}
- (void) removeJoinFrom:(MCPAttribute *) iFrom to:(MCPAttribute *) iTo
{
NSDictionary *theJoinDict = [[NSDictionary alloc] initWithObjectsAndKeys:self, @"relation", iFrom, @"origin", iTo, @"destination", nil];
NSUInteger i = [joins indexOfObject:theJoinDict];
if (NSNotFound != i) {
[[self objectInJoinsAtIndex:i] invalidate];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
[theJoinDict release];
}
/*
- (void) unjoinAttribute:(MCPAttribute *) iAttribute
{
unsigned int i = 0;
#warning HAVE to rewrite this code!!!
if ([[iAttribute classDescription] isEqual:origin]) {
for (i=0; ([joins count] != i) && ([iAttribute isEqual:[(MCPJoin *)[joins objectAtIndex:i] origin]]); ++i) {
}
}
if ([[iAttribute classDescription] isEqual:destination]) {
for (i=0; ([joins count] != i) && ([iAttribute isEqual:[(MCPJoin *)[joins objectAtIndex:i] destination]]); ++i) {
}
}
if ((0 == i) || ([joins count] == i)) { // No joins found using this attribute.
return;
}
[self removeJoinFrom:[(MCPJoin *)[joins objectAtIndex:i] origin] to:[(MCPJoin *)[joins objectAtIndex:i] destination]];
}
*/
#pragma mark Setters
- (void) setDestination:(MCPClassDescription *) iDestination
{
if (iDestination != destination) {
[destination removeObjectFromIncomingsAtIndex:[[destination incomings] indexOfObjectIdenticalTo:self]];
destination = iDestination;
[destination insertObject:self inIncomingsAtIndex:0];
[self invalidateJoins];
}
}
- (void) setName:(NSString *) iName
{
if (iName != name) {
[name release];
name = [iName retain];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
}
- (void) setDeleteRule:(MCPRelationDeleteRule) iDeleteRule
{
if (iDeleteRule != deleteRule) { // Don't do the notification for nothing!!!
deleteRule = iDeleteRule;
[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
}
- (void) setInverseRelation:(MCPRelation *) iInverseRelation
{
if (iInverseRelation != inverseRelation) {
[inverseRelation release];
inverseRelation = [iInverseRelation retain];
[inverseRelation setInverseRelation:self];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
}
- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(NSUInteger) index
{
[joins insertObject:iJoin atIndex:index];
}
- (void) removeObjectFromJoinsAtIndex:(NSUInteger) index
{
[joins removeObjectAtIndex:index];
}
- (void) setIsToMany:(BOOL) iIsToMany
{
if (iIsToMany != isToMany) { // Don't do the notification for nothing!!!
isToMany = iIsToMany;
[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
}
- (void) setIsMandatory:(BOOL) iIsMandatory
{
if (iIsMandatory != isMandatory) { // Don't do the notification for nothing!!!
isMandatory = iIsMandatory;
[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
}
- (void) setOwnsDestintation:(BOOL) iOwnsDestination
{
if (iOwnsDestination != ownsDestination) { // Don't do the notification for nothing!!!
ownsDestination = iOwnsDestination;
[[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[origin model]];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:origin];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
}
#pragma mark Getters
- (NSString *) name
{
return name;
}
- (MCPRelationDeleteRule) deleteRule
{
return deleteRule;
}
- (MCPRelation *) inverseRelation
{
return inverseRelation;
}
- (MCPClassDescription *) origin
{
return origin;
}
- (MCPClassDescription *) destination
{
return destination;
}
- (NSArray *) joins
{
return [NSArray arrayWithArray:joins];
}
- (NSUInteger) countOfJoins
{
return [joins count];
}
- (MCPJoin *) objectInJoinsAtIndex:(NSUInteger) index
{
return (MCPJoin *)((NSNotFound != index) ? [joins objectAtIndex:index] : nil);
}
- (NSUInteger) indexOfJoinIdenticalTo:(id) iJoin
{
return [joins indexOfObjectIdenticalTo:iJoin];
}
- (BOOL) isToMany
{
return isToMany;
}
- (BOOL) isMandatory
{
return isMandatory;
}
- (BOOL) ownsDestination
{
return ownsDestination;
}
#pragma mark Some Usefull methods
- (MCPAttribute *) destinationAttributeForOrigin:(MCPAttribute *) iFrom
{
NSUInteger i;
for (i=0; ([joins count] != i) && ([[(MCPJoin *)[joins objectAtIndex:i] origin] isEqual:iFrom]); ++i) {
}
return ([joins count] == i) ? nil : [(MCPJoin *)[joins objectAtIndex:i] destination];
}
- (MCPAttribute *) originAttributeForDestination:(MCPAttribute *) iTo
{
NSUInteger i;
for (i=0; ([joins count] != i) && ([[(MCPJoin *)[joins objectAtIndex:i] destination] isEqual:iTo]); ++i) {
}
return ([joins count] == i) ? nil : [(MCPJoin *)[joins objectAtIndex:i] origin];
}
#pragma mark Some general methods:
- (BOOL) isEqual:(id) iObject
// Equal to another relation, if they have the same name and same origin and destination class descriptions (they have the same names).
// Equal to a string (NSString), if the name of the relation is equal to the string.
{
if ([iObject isKindOfClass:[MCPRelation class]]) {
MCPRelation *theRelation = (MCPRelation *) iObject;
return ([name isEqualToString:[theRelation name]]) && ([[self origin] isEqual:[theRelation origin]]) && ([[self destination] isEqual:[theRelation destination]]);
}
if ([iObject isKindOfClass:[NSString class]]) {
return [name isEqualToString:(NSString *)iObject];
}
return NO;
}
- (NSString *) descriptionWithLocale:(NSDictionary *) locale
{
NSMutableString *theRet = [NSMutableString stringWithFormat:@"MCPRelation named %@, going from %@ to %@. Joins :\n", name, [origin name], [destination name]];
NSUInteger i;
for (i = 0; [joins count] != i; ++i) {
MCPJoin *tmpJoin = (MCPJoin *)[joins objectAtIndex:i];
[theRet appendFormat:@"\t\t%@ == %@\n", [[tmpJoin origin] name], [[tmpJoin destination] name]];
}
return theRet;
}
#pragma mark For debugging the retain counting
- (id) retain
{
[super retain];
return self;
}
- (void) release
{
[super release];
return;
}
@end
@implementation MCPRelation (Private)
#pragma mark Making some work
- (void) invalidateJoins
{
while ([joins count]) {
[[self objectInJoinsAtIndex:0] invalidate];
}
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
#pragma mark Setters
- (void) setOrigin:(MCPClassDescription *) iOrigin
{
if (iOrigin != origin) {
[origin removeObjectFromRelationsAtIndex:[[origin relations] indexOfObjectIdenticalTo:self]];
origin = iOrigin;
[origin insertObject:self inRelationsAtIndex:[origin countOfRelations]];
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
[self invalidateJoins];
}
}
/*
- (void) setJoins:(NSArray *) iJoins
{
if (iJoins != joins) {
unsigned int i;
if (joins) {
[self invalidateJoins];
}
else {
joins = [[NSMutableArray alloc] init];
}
for (i=0; [iJoins count] != i; ++i) {
[self addJoinFrom:[(MCPJoin *)[iJoins objectAtIndex:i] origin] to:[(MCPJoin *)[iJoins objectAtIndex:i] destination]];
}
[[NSNotificationCenter defaultCenter] postNotificationName:MCPRelationChangedNotification object:self];
}
}
*/
#pragma mark Getters
- (MCPModel *) model
{
return [origin model];
}
#pragma mark Fro the controller layer and the UI
- (void) addNewDefaultJoin // Usefull for the interface, to be able to create a new join by just using a binding.
{
[self addJoinFrom:[origin objectInAttributesAtIndex:0] to:[destination objectInAttributesAtIndex:0]];
}
@end