diff options
Diffstat (limited to 'Frameworks/MCPKit/MCPEntrepriseKit')
20 files changed, 4155 insertions, 0 deletions
diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.h new file mode 100644 index 00000000..1a474346 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.h @@ -0,0 +1,106 @@ +// +// $Id: MCPAttribute.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPAttribute.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +@class MCPModel; +@class MCPClassDescription; +@class MCPRelation; +@class MCPJoin; + +@interface MCPAttribute : NSObject < NSCoding > +{ +@protected + MCPClassDescription *classDescription; // ClassDescription of which the attribute is attribute + NSString *name; // Name of the attribute (Obj-C side) + Class valueClass; // Class used by the attribute (or NULL if the internal type is not an object) + NSString *internalType; // Name of the class, or type used for the class definition + NSString *externalName; // Name of the corresponding column in the DB + NSString *externalType; // Type used to store the attribute (in the DB) + unsigned int width; // Width (for storing by the DB) + BOOL allowsNull; // Attribute can be null + BOOL autoGenerated; // Attribute is auto generated by the DB + BOOL isPartOfKey; // Attribute is part of theprimary key of the class description + BOOL isPartOfIdentity; // Attribute is part of the idclass description of the class description + BOOL hasAccessor; // Does this attribute have an accessor + id defaultValue; // Default value of the attribute + NSMutableArray *joins; // An array of the joins using this attribute +} + +#pragma mark Class methods ++ (void) initialize; + +#pragma mark Life cycle +- (id) initForClassDescription:(MCPClassDescription *) iClassDescription withName:(NSString *) iName; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Setters +- (void) setName:(NSString *) iName; +- (void) setValueClass:(Class) iValueClass; +- (void) setInternalType:(NSString *) iInternalType; +- (void) setExternalType:(NSString *) iExternalType; +- (void) setExternalName:(NSString *) iExternalName; +- (void) setWidth:(unsigned int) iWidth; +- (void) setAllowsNull:(BOOL) iAllowsNull; +- (void) setAutoGenerated:(BOOL) iAutoGenerated; +- (void) setIsPartOfKey:(BOOL) iIsPartOfKey; +- (void) setIsPartOfIdentity:(BOOL) iIsPartOfIdentity; +- (void) setHasAccessor:(BOOL) iHasAccessor; +- (void) setDefaultValue:(id) iDefaultValue; +- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(unsigned int) index; +- (void) removeObjectFromJoinsAtIndex:(unsigned int) index; +//- (void) addRelation:(MCPRelation *) iRelation; +//- (void) removeRelation:(MCPRelation *) iRelation; + +#pragma mark Getters +- (MCPClassDescription *) classDescription; +- (NSString *) name; +- (Class) valueClass; +- (NSString *) valueClassName; +- (NSString *) internalType; +- (NSString *) externalName; +- (NSString *) externalType; +- (unsigned int) width; +- (BOOL) allowsNull; +- (BOOL) autoGenerated; +- (BOOL) isPartOfKey; +- (BOOL) isPartOfIdentity; +- (BOOL) hasAccessor; +- (id) defaultValue; +- (unsigned int) countOfJoins; +- (MCPJoin *) objectInJoinsAtIndex:(unsigned int) index; +- (unsigned int) indexOfJoinIdenticalTo:(id) iJoin; + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.m new file mode 100644 index 00000000..34b50284 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPAttribute.m @@ -0,0 +1,466 @@ +// +// $Id: MCPAttribute.m 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPAttribute.m +// MCPkit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPAttribute.h" + +#import "MCPEntrepriseNotifications.h" + +#import "MCPModel.h" +#import "MCPClassDescription.h" +#import "MCPRelation.h" +#import "MCPJoin.h" + +static NSArray *MCPRecognisedInternalType; + +@interface MCPAttribute (Private) + +- (void)setValueClassName:(NSString *) iClassName; + +@end + +@implementation MCPAttribute + +#pragma mark Class methods ++ (void) initialize +{ + if (self == [MCPAttribute class]) { + [self setVersion:010101]; // Ma.Mi.Re -> MaMiRe + MCPRecognisedInternalType = [[NSArray alloc] initWithObjects:@"NSCalendarDate", @"NSData", @"NSNumber", @"NSString", nil]; + [self setKeys:[NSArray arrayWithObject:@"internalType"] triggerChangeNotificationsForDependentKey:@"valueClassName"]; + [self setKeys:[NSArray arrayWithObject:@"valueClassName"] triggerChangeNotificationsForDependentKey:@"internalType"]; + } + return; +} + + +#pragma mark Life cycle +- (id) initForClassDescription:(MCPClassDescription *) iClassDescription withName:(NSString *) iName +{ + self = [super init]; + { + classDescription = iClassDescription; + [self setName:iName]; +// relations = (NSMutableArray *)(CFArrayCreateMutable (kCFAllocatorDefault, 0, NULL)); + joins = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc +{ +// NSArray *theRelations; +// unsigned int i; + + [name release]; + [internalType release]; + [externalName release]; + [externalType release]; + [defaultValue release]; +/* + while ([relations count]) { + [(MCPRelation *)[relations objectAtIndex:0] unjoinAttribute:self]; + } +// By now relation should be empty anyway... + [relations release]; + */ + while ([joins count]) { + [[self objectInJoinsAtIndex:0] invalidate]; + } + // By now the joins array should be empty + [joins release]; + [super dealloc]; +} + + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + NSString *theClassName = [decoder decodeObjectForKey:@"MCPvalueClassName"]; + + classDescription = [decoder decodeObjectForKey:@"MCPclassDescription"]; + [self setName:[decoder decodeObjectForKey:@"MCPname"]]; + if (theClassName) { + [self setValueClass:NSClassFromString(theClassName)]; + } + [self setInternalType:[decoder decodeObjectForKey:@"MCPinternalType"]]; + [self setExternalName:[decoder decodeObjectForKey:@"MCPexternalName"]]; + [self setExternalType:[decoder decodeObjectForKey:@"MCPexternalType"]]; + [self setWidth:(unsigned int)[decoder decodeInt32ForKey:@"MCPwidth"]]; + [self setAllowsNull:[decoder decodeBoolForKey:@"MCPallowsNull"]]; + [self setAutoGenerated:[decoder decodeBoolForKey:@"MCPautoGenerated"]]; + [self setIsPartOfKey:[decoder decodeBoolForKey:@"MCPisPartOfKey"]]; + [self setIsPartOfIdentity:[decoder decodeBoolForKey:@"MCPisPartOfIdentity"]]; + [self setHasAccessor:[decoder decodeBoolForKey:@"MCPhasAccessor"]]; + [self setDefaultValue:[decoder decodeObjectForKey:@"MCPdefaultValue"]]; +// Not sure that the next line is working (getting an array holding weak references), hence doing the thing expelcitly: +// relations = [[decoder decodeObjectForKey:@"MCPrelations"] retain]; +// relations = (NSMutableArray *)(CFArrayCreateMutable (kCFAllocatorDefault, 0, NULL)); +// [relations addObjectsFromArray:[decoder decodeObjectForKey:@"MCPrelations"]]; + joins = [[NSMutableArray alloc] init]; // Will be filled in when the relations are read in. + } + else { + NSLog(@"For some reason, unable to decode MCPAttribute from the coder!!!"); + } +// NSLog(@"MAKING a new object : %@", self); + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + NSString *theValueClassName; + + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPAttribute -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } +// theValueClassName = (valueClass) ? [valueClass className] : nil; + theValueClassName = (valueClass) ? NSStringFromClass(valueClass) : nil; + [encoder encodeObject:[self classDescription] forKey:@"MCPclassDescription"]; + [encoder encodeObject:[self name] forKey:@"MCPname"]; + if (theValueClassName) { + [encoder encodeObject:theValueClassName forKey:@"MCPvalueClassName"]; + } + [encoder encodeObject:[self internalType] forKey:@"MCPinternalType"]; + [encoder encodeObject:[self externalName] forKey:@"MCPexternalName"]; + [encoder encodeObject:[self externalType] forKey:@"MCPexternalType"]; + [encoder encodeInt32:(int32_t)[self width] forKey:@"MCPwidth"]; + [encoder encodeBool:[self allowsNull] forKey:@"MCPallowsNull"]; + [encoder encodeBool:[self autoGenerated] forKey:@"MCPautoGenerated"]; + [encoder encodeBool:[self isPartOfKey] forKey:@"MCPisPartOfKey"]; + [encoder encodeBool:[self isPartOfIdentity] forKey:@"MCPisPartOfIdentity"]; + [encoder encodeBool:[self hasAccessor] forKey:@"MCPhasAccessor"]; + [encoder encodeObject:[self defaultValue] forKey:@"MCPdefaultValue"]; +// [encoder encodeObject:relations forKey:@"MCPrelation"]; + // We don't have to save the joins here ... the joins are saving there attributes. + // The links are recreated when the joins are decoded. +} + +#pragma mark Setters +- (void) setName:(NSString *) iName +{ + if (iName != name) { + [name release]; + name = [iName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setValueClass:(Class) iValueClass +{ + if (iValueClass != valueClass) { + valueClass = iValueClass; + if (valueClass) { // Not nil : set the internalType accrodingly. + // [internalType release]; + // internalType = [[valueClass className] copy]; +// [self setValue:[NSString stringWithString:[valueClass className]] forKey:@"internalType"]; + [self setValue:[NSString stringWithString:NSStringFromClass(valueClass)] forKey:@"internalType"]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setInternalType:(NSString *) iInternalType +{ + if (iInternalType != internalType) { + [internalType release]; + internalType = [iInternalType retain]; + if ([MCPRecognisedInternalType containsObject:internalType]) { + [self setValueClass:NSClassFromString(internalType)]; +// By itself does NOT provide observers the update. +// but see setKeys:triggerChangeNotificationsForDependentKey... (in +initialize). + } + else { + [self setValueClass:nil]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setExternalType:(NSString *) iExternalType +{ + if (iExternalType != externalType) { + [externalType release]; + externalType = [iExternalType retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setExternalName:(NSString *) iExternalName +{ + if (iExternalName != externalName) { + [externalName release]; + externalName = [iExternalName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setWidth:(unsigned int) iWidth +{ + if (iWidth != width) { + width = iWidth; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setAllowsNull:(BOOL) iAllowsNull +{ + if (iAllowsNull != allowsNull) { + allowsNull = iAllowsNull; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setAutoGenerated:(BOOL) iAutoGenerated +{ + if (iAutoGenerated != autoGenerated) { + autoGenerated = iAutoGenerated; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setIsPartOfKey:(BOOL) iIsPartOfKey +{ + if (iIsPartOfKey != isPartOfKey) { + isPartOfKey = iIsPartOfKey; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setIsPartOfIdentity:(BOOL) iIsPartOfIdentity +{ + if (iIsPartOfIdentity != isPartOfIdentity) { + isPartOfIdentity = iIsPartOfIdentity; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setHasAccessor:(BOOL) iHasAccessor +{ + if (iHasAccessor != hasAccessor) { + hasAccessor = iHasAccessor; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) setDefaultValue:(id) iDefaultValue +{ + if (iDefaultValue != defaultValue) { + [defaultValue release]; + defaultValue = [iDefaultValue retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:[classDescription model]]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:classDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPAttributeChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(unsigned int) index +{ + [joins insertObject:iJoin atIndex:index]; +} + +- (void) removeObjectFromJoinsAtIndex:(unsigned int) index +{ + [joins removeObjectAtIndex:index]; +} + +/* +- (void) addRelation:(MCPRelation *) iRelation +{ +// Following implementation make sure that a given relation is only added once... but I don't see the reason for that to be true. + /* if (NSNotFound == [relations indexOfObjectIdenticalTo:iRelation]) { + [relations addObject:iRelation]; + } + *//* + [relations addObject:iRelation]; +} + +- (void) removeRelation:(MCPRelation *) iRelation +{ +// Following implementation needs only one reference to a given relation to be working properly (not true) +// [relations removeObjectIdenticalTo:iRelation]; + unsigned int i; + + i = [relations indexOfObjectIdenticalTo:iRelation]; + if (NSNotFound != i) { + [relations removeObjectAtIndex:i]; + } +// If the relation is there more than once, remove it only once. +} +*/ + +#pragma mark Getters +- (MCPClassDescription *) classDescription +{ + return classDescription; +} + +- (NSString *) name +{ + return name; +} + +- (Class) valueClass +{ + return valueClass; +} + +- (NSString *) valueClassName +{ + return NSStringFromClass(valueClass); +} + +- (NSString *) internalType +{ + return internalType; +} + +- (NSString *) externalName +{ + return externalName; +} + +- (NSString *) externalType +{ + return externalType; +} + +- (unsigned int) width +{ + return width; +} + +- (BOOL) allowsNull +{ + return allowsNull; +} + +- (BOOL) autoGenerated +{ + return autoGenerated; +} + +- (BOOL) isPartOfKey +{ + return isPartOfKey; +} + +- (BOOL) isPartOfIdentity +{ + return isPartOfIdentity; +} + +- (BOOL) hasAccessor +{ + return hasAccessor; +} + +- (id) defaultValue +{ + return defaultValue; +} + +- (unsigned int) countOfJoins +{ + return [joins count]; +} + +- (MCPJoin *) objectInJoinsAtIndex:(unsigned int) index +{ + return (MCPJoin *)((NSNotFound != index) ? [joins objectAtIndex:index] : nil); +} + +- (unsigned int) indexOfJoinIdenticalTo:(id) iJoin +{ + return [joins indexOfObjectIdenticalTo:iJoin]; +} + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject +// Equal to another attribute, if they have the same name and same class description. +// Equal to a string (NSString), if the name of the attribute is equal to the string. +{ + if ([iObject isKindOfClass:[MCPAttribute class]]) { + MCPAttribute *theAttribute = (MCPAttribute *) iObject; + + return ([name isEqualToString:[theAttribute name]]) && ([classDescription isEqual:[theAttribute classDescription]]); + } + if ([iObject isKindOfClass:[NSString class]]) { + return [name isEqualToString:(NSString *)iObject]; + } + return NO; +} + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + return self; +} + +- (void) release +{ + [super release]; + return; +} + +@end + +@implementation MCPAttribute (Private) + +- (void)setValueClassName:(NSString *) iClassName +{ + if (NSClassFromString(iClassName) != valueClass) { + [self setValueClass:NSClassFromString(iClassName)]; + } +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.h new file mode 100644 index 00000000..adc66141 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.h @@ -0,0 +1,47 @@ +// +// $Id: MCPClassDescription+MCPEntreprise.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPClassDescription+MCPEntreprise.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 01/11/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> +#import "MCPClassDescription.h" + +@interface MCPClassDescription (MCPEntreprise) + +#pragma mark Pseudo getters (for NSClassDescription overload) +- (NSArray *) attributeKeys; +- (NSString *) inverseRelationshipKey:(NSString *) relationshipKey; +- (NSArray *) toManyRelationshipKeys; +- (NSArray *) toOneRelationshipKeys; + +#pragma mark Specifics for MCPObject +- (NSArray *) primaryKeyAttributes; +- (NSArray *) identityAttributes; +- (MCPAttribute *) attributeWithName: (NSString *) iName; +- (MCPRelation *) relationWithName:(NSString *) iRelationName; +- (BOOL) singleIntAutoGenKey; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.m new file mode 100644 index 00000000..4c912a1f --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+MCPEntreprise.m @@ -0,0 +1,186 @@ +// +// $Id: MCPClassDescription+MCPEntreprise.m 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPClassDescription+MCPEntreprise.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 01/11/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPClassDescription+MCPEntreprise.h" + +#import "MCPAttribute.h" +#import "MCPRelation.h" + +@implementation MCPClassDescription (MCPEntreprise) + +#pragma mark Pseudo getters (for NSClassDescription overload) +- (NSArray *) attributeKeys +{ + NSArray *theRet; + NSMutableArray *theKeys =[[NSMutableArray alloc] init]; + unsigned int i; + + for (i=0; i != [self countOfAttributes]; ++i) { + [theKeys insertObject:[(MCPAttribute *)[self objectInAttributesAtIndex:i] name] atIndex:i]; + } + theRet = [NSArray arrayWithArray:theKeys]; + [theKeys release]; + return theRet; +} + +- (NSString *) inverseRelationshipKey:(NSString *) relationshipKey +{ + unsigned int index = [self indexOfRelation:relationshipKey]; + + if (NSNotFound != index) { + MCPRelation *theRelation; + + theRelation = (MCPRelation *)[self objectInRelationsAtIndex:index]; + return [[theRelation inverseRelation] name]; + } + return nil; +} + +- (NSArray *) toManyRelationshipKeys +{ + NSArray *theRet; + NSMutableArray *theToManyRel = [[NSMutableArray alloc] init]; + unsigned int i, j; + + j=0; + for (i=0; i != [self countOfRelations]; ++i) { + MCPRelation *theRelation = (MCPRelation *)[self objectInRelationsAtIndex:i]; + + if ([theRelation isToMany]) { + [theToManyRel insertObject:[theRelation name] atIndex:j]; + ++j; + } + } + theRet = [NSArray arrayWithArray:theToManyRel]; + [theToManyRel release]; + return theRet; +} + +- (NSArray *) toOneRelationshipKeys; +{ + NSArray *theRet; + NSMutableArray *theToOneRel = [[NSMutableArray alloc] init]; + unsigned int i, j; + + j=0; + for (i=0; i != [self countOfRelations]; ++i) { + MCPRelation *theRelation = (MCPRelation *)[self objectInRelationsAtIndex:i]; + + if (! [theRelation isToMany]) { + [theToOneRel insertObject:[theRelation name] atIndex:j]; + ++j; + } + } + theRet = [NSArray arrayWithArray:theToOneRel]; + [theToOneRel release]; + return theRet; +} + +#pragma mark Specifics for MCPObject +- (NSArray *) primaryKeyAttributes +{ + NSMutableArray *theRet = [NSMutableArray array]; + unsigned int i, j; + + j = 0; + for (i=0; i != [self countOfAttributes]; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *)[self objectInAttributesAtIndex:i]; + + if ([theAttribute isPartOfKey]) { + [theRet insertObject:theAttribute atIndex:j]; + ++j; + } + } + return (NSArray *)theRet; +} + +- (NSArray *) identityAttributes +{ + NSMutableArray *theRet = [NSMutableArray array]; + unsigned int i, j; + + j = 0; + for (i=0; i != [self countOfAttributes]; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *)[self objectInAttributesAtIndex:i]; + + if ([theAttribute isPartOfIdentity]) { + [theRet insertObject:theAttribute atIndex:j]; + ++j; + } + } + return (NSArray *)theRet; +} + +- (MCPAttribute *) attributeWithName: (NSString *) iName +{ +// This type of implementation is NOT working : most likely the isEqual method is called on iName rather than on the objects of the array +/* + unsigned int index = [self indexOfAttribute:iName]; + + return (NSNotFound != index) ? (MCPAttribute *)[self objectInAttributesAtIndex:index] : nil ; +*/ + unsigned int i; + + for (i = 0; [attributes count] != i; ++i) { + if ([[(MCPAttribute *)[attributes objectAtIndex:i] name] isEqualToString:iName]) { + return (MCPAttribute *)[attributes objectAtIndex:i]; + } + } + return nil; +} + +- (MCPRelation *) relationWithName:(NSString *) iRelationName +{ +// This type of implementation is NOT working : most likely the isEqual method is called on iName rather than on the objects of the array +/* unsigned int index = [relations indexOfObject:iRelationName]; + + return (NSNotFound != index) ? (MCPRelation *)[relations objectAtIndex:index] : nil; +*/ + unsigned int i; + + for (i = 0; [relations count] != i; ++i) { + if ([[(MCPRelation *)[relations objectAtIndex:i] name] isEqualToString:iRelationName]) { + return (MCPRelation *)[relations objectAtIndex:i]; + } + } + return nil; +} + +- (BOOL) singleIntAutoGenKey +{ + NSArray *theKeys = [self primaryKeyAttributes]; + + if (1 == [theKeys count]) { + MCPAttribute *theSingleKey = (MCPAttribute *)[theKeys objectAtIndex:0]; + + return [theSingleKey autoGenerated] && [[theSingleKey externalType] isEqualToString:@"INT"]; + } + return NO; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+Private.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+Private.h new file mode 100644 index 00000000..46c8a626 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription+Private.h @@ -0,0 +1,45 @@ +// +// $Id: MCPClassDescription+Private.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPClassDescription+Private.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPClassDescription.h" + +@interface MCPClassDescription (Private) + +#pragma mark Setters +- (void) setAttributes:(NSArray *) iAttributes; +- (void) setRelations:(NSArray *) iRelations; +- (void) insertObject:(MCPRelation *) iRelation inIncomingsAtIndex:(unsigned int) index; +- (void) removeObjectFromIncomingsAtIndex:(unsigned int) index; + +#pragma mark Getters +- (NSArray *) incomings; +- (unsigned int) countOfIncomings; +- (MCPRelation *) objectInIncomingsAtIndex:(unsigned int) index; +- (unsigned int) indexOfIncoming:(id) iRelation; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.h new file mode 100644 index 00000000..c5119797 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.h @@ -0,0 +1,93 @@ +// +// $Id: MCPClassDescription.h 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPClassDescription.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +@class MCPModel; +@class MCPAttribute; +@class MCPRelation; +@class MCPRelation; + +@interface MCPClassDescription : NSClassDescription <NSCoding> +{ +@protected + MCPModel *model; // The model where we stand + NSString *name; // Name of the class (can not use className, as it is already used by NSObject). + NSString *externalName; // Name of the table for storage + NSMutableArray *attributes; // array of the attributes of the class description + NSMutableArray *relations; // array of the relations of the class description (both origin and destination) + NSMutableArray *incomings; // array if the INCOMMING relation (just to be sure we are able to invalidate those if necessary) + Class representedClass; // the class object that the description represents. +} + +// This correspond to the method singleIntAutoGenKey in the category MCPEntreprise... which name should I change... + +#pragma mark Class methods ++ (void) initialize; + +#pragma mark Life cycle +- (id) initInModel:(MCPModel *) iModel withName:(NSString *) iName; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Making new attributes and relations +- (MCPAttribute *) addNewAttributeWithName:(NSString *) iName inPosition:(int) index; +- (MCPRelation *) addNewRelationTo:(MCPClassDescription *) iTo name:(NSString *) iName inPostion:(int) index; + +#pragma mark Setters +- (void) setName:(NSString *) iName; +- (void) setExternalName:(NSString *) iExternalName; +- (void) insertObject:(MCPAttribute *) iAttribute inAttributesAtIndex:(unsigned int) index; +- (void) removeObjectFromAttributesAtIndex:(unsigned int) index; +- (void) insertObject:(MCPRelation *) iRelation inRelationsAtIndex:(unsigned int) index; +- (void) removeObjectFromRelationsAtIndex:(unsigned int) index; + +#pragma mark Getters +- (MCPModel *) model; +- (NSString *) name; +- (NSString *) externalName; +- (NSArray *) attributes; +- (unsigned int) countOfAttributes; +- (MCPAttribute *) objectInAttributesAtIndex:(unsigned int) index; +- (unsigned int) indexOfAttribute:(id) iAttribute; +- (NSArray *) relations; +- (unsigned int) countOfRelations; +- (MCPRelation *) objectInRelationsAtIndex:(unsigned int) index; +- (unsigned int) indexOfRelation:(id) iRelation; +- (Class) representedClass; + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject; + +#pragma mark Output for logging +- (NSString *) descriptionWithLocale:(NSDictionary *) locale; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.m new file mode 100644 index 00000000..fcd79e01 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPClassDescription.m @@ -0,0 +1,372 @@ +// +// $Id: MCPClassDescription.m 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPClassDescription.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPClassDescription.h" +#import "MCPClassDescription+Private.h" + +#import "MCPEntrepriseNotifications.h" + +#import "MCPModel.h" +#import "MCPAttribute.h" +#import "MCPRelation.h" + +@implementation MCPClassDescription + +#pragma mark Class methods ++ (void) initialize +{ + if (self = [MCPClassDescription class]) { + [self setVersion:010101]; // Major.Minor.Revision -> MaMiRe + } + return; +} + +#pragma mark Life cycle +- (id) initInModel:(MCPModel *) iModel withName:(NSString *) iName +{ + self = [super init]; + if (self) { + model = iModel; + [self setName:iName]; + attributes = [[NSMutableArray alloc] init]; + relations = [[NSMutableArray alloc] init]; + incomings = [[NSMutableArray alloc] init]; + representedClass = nil; + // NSLog(@"MAKING a new object : %@", self); + } + return self; +} + +- (void) dealloc +{ +// NSArray *theRelations; +// unsigned int i; + + [name release]; + [externalName release]; + [attributes release]; + while ([relations count]) { + [(MCPRelation *)[relations objectAtIndex:0] invalidateRelation]; + } + [relations release]; + while ([incomings count]) { + [(MCPRelation *)[incomings objectAtIndex:0] invalidateRelation]; + } + [incomings release]; + [super dealloc]; +} + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + model = [decoder decodeObjectForKey:@"MCPmodel"]; +// NSLog(@"in MCPClassDescription initWithCoder, model = %@ (pointer = %p)", model, model); + [self setName:[decoder decodeObjectForKey:@"MCPname"]]; + [self setExternalName:[decoder decodeObjectForKey:@"MCPexternalName"]]; + [self setAttributes:[decoder decodeObjectForKey:@"MCPattributes"]]; +// [self setRelations:[decoder decodeObjectForKey:@"MCPrelations"]]; + relations = [[NSMutableArray alloc] init]; + incomings = [[NSMutableArray alloc] init]; + representedClass = nil; + [decoder decodeObjectForKey:@"MCPrelations"]; // The relation get linked properly while initted. + } + else { + NSLog(@"For some reason, unable to decode MCPClassDescription from the coder!!!"); + } + + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPClassDescription -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } +// [encoder encodeObject:[self model] forKey:@"MCPmodel"]; + [encoder encodeConditionalObject:[self model] forKey:@"MCPmodel"]; + [encoder encodeObject:[self name] forKey:@"MCPname"]; + [encoder encodeObject:[self externalName] forKey:@"MCPexternalName"]; + [encoder encodeObject:[self attributes] forKey:@"MCPattributes"]; + [encoder encodeObject:[self relations] forKey:@"MCPrelations"]; + [encoder encodeObject:@"1.1.1" forKey:@"MCPversion"]; + return; +} + +#pragma mark Making new attributes and relations +- (MCPAttribute *) addNewAttributeWithName:(NSString *) iName inPosition:(int) index +{ + MCPAttribute *theAttribute = [[MCPAttribute alloc] initForClassDescription:self withName:iName]; + +// [self addAttribute:theAttribute]; + [self insertObject:theAttribute inAttributesAtIndex:(index < 0) ? ([self countOfAttributes] + index + 1) : index]; + [theAttribute release]; + return theAttribute; +} + +- (MCPRelation *) addNewRelationTo:(MCPClassDescription *) iTo name:(NSString *) iName inPostion:(int) index +{ + MCPRelation *theRelation = [[MCPRelation alloc] initWithName:iName from:self to:iTo]; + + [theRelation release]; + return theRelation; +} + +#pragma mark Setters +- (void) setName:(NSString *) iName +{ + if (iName != name) { + [name release]; + name = [iName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; + representedClass = nil; + } +} + +- (void) setExternalName:(NSString *) iExternalName +{ + if (iExternalName != externalName) { + [externalName release]; + externalName = [iExternalName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPAttribute *) iAttribute inAttributesAtIndex:(unsigned int) index +{ + [attributes insertObject:iAttribute atIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; +} + +- (void) removeObjectFromAttributesAtIndex:(unsigned int) index +{ + [attributes removeObjectAtIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; +} + +- (void) insertObject:(MCPRelation *) iRelation inRelationsAtIndex:(unsigned int) index +{ + [relations insertObject:iRelation atIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; +} + +- (void) removeObjectFromRelationsAtIndex:(unsigned int) index +{ + [relations removeObjectAtIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; +} + +#pragma mark Getters +- (MCPModel *) model +{ + return model; +} + +- (NSString *) name +{ + return name; +} + +- (NSString *) externalName +{ + return externalName; +} + +- (NSArray *) attributes +{ + return [NSArray arrayWithArray:attributes]; +} + +- (unsigned int) countOfAttributes +{ + return [attributes count]; +} + +- (MCPAttribute *) objectInAttributesAtIndex:(unsigned int) index +{ + return (MCPAttribute *)((NSNotFound != index) ? [attributes objectAtIndex:index] : nil); +} + +- (unsigned int) indexOfAttribute:(id) iAttribute +{ + return [attributes indexOfObject:iAttribute]; +} + +- (NSArray *) relations +{ + return [NSArray arrayWithArray:relations]; +} + +- (unsigned int) countOfRelations +{ + return [relations count]; +} + +- (MCPRelation *) objectInRelationsAtIndex:(unsigned int) index +{ + return (MCPRelation *)((NSNotFound != index) ? [relations objectAtIndex:index] : nil); +} + +- (unsigned int) indexOfRelation:(id) iRelation +{ + return [relations indexOfObject:iRelation]; +} + +- (Class) representedClass +{ + if (representedClass) { + return representedClass; + } + representedClass = NSClassFromString(name); + return representedClass; +} + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject +// Equal to another class description if they have the same name. +// Equal to a string if the string is equal to the className of the class description. +{ + if ([iObject isKindOfClass:[MCPClassDescription class]]) { + return [name isEqualToString:[(MCPClassDescription *)iObject name]]; + } + if ([iObject isKindOfClass:[NSString class]]) { + return [name isEqualToString:(NSString *)iObject]; + } + return NO; +} + +/* +- (NSString *) description +{ + return [NSString stringWithFormat:@"<MCPClassDescription for class named %@ : %p>", [self name], self]; +} + +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + return [self description]; +} +*/ + +#pragma mark Output for logging +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + NSMutableString *theOutput = [NSMutableString string]; + unsigned i; + + [theOutput appendFormat:@"MCPClassDescription for class : %@ (table : %@)\n", [self name], [self externalName]]; + for (i=0; [attributes count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [attributes objectAtIndex:i]; + + [theOutput appendFormat:@"attribute %u, name = %@, column = %@. Allows null : %c\n", i, [theAttribute name], [theAttribute externalName], ([theAttribute allowsNull] ? 'Y' : 'N')]; + } + return theOutput; +} + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + return self; +} + +- (void) release +{ + [super release]; + return; +} + +@end + +@implementation MCPClassDescription (Private) + +#pragma mark Setters +- (void) setAttributes:(NSArray *) iAttributes +{ + if (iAttributes != attributes) { + [attributes release]; + attributes = [[NSMutableArray alloc] initWithArray:iAttributes]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; + } +} + +- (void) setRelations:(NSArray *) iRelations +{ + if (iRelations != relations) { + [relations release]; + relations = [[NSMutableArray alloc] initWithArray:iRelations]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:model]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPClassDescriptionChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPRelation *) iRelation inIncomingsAtIndex:(unsigned int) index +{ + if ([iRelation destination] == self) { + [incomings insertObject:iRelation atIndex:index]; + } + else { + NSLog(@"in -[MCPClassDescription+Private insertObject:inIncomingsAtIndex:]. ERRROR : self is NOT the destination of the relation"); + } +} + +- (void) removeObjectFromIncomingsAtIndex:(unsigned int) index +{ + [incomings removeObjectAtIndex:index]; +} + +#pragma mark Getters +- (NSArray *) incomings +{ + return [NSArray arrayWithArray:incomings]; +} + +- (unsigned int) countOfIncomings +{ + return [incomings count]; +} + +- (MCPRelation *) objectInIncomingsAtIndex:(unsigned int) index +{ + return (MCPRelation *)[incomings objectAtIndex:index]; +} + +- (unsigned int) indexOfIncoming:(id) iRelation +{ + return [incomings indexOfObject:iRelation]; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.h new file mode 100644 index 00000000..496a448c --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.h @@ -0,0 +1,36 @@ +// +// $Id: MCPEntrepriseNotifications.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPEnterpriseNotifications.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +#pragma mark Name for notification + +extern NSString *MCPModelChangedNotification; +extern NSString *MCPClassDescriptionChangedNotification; +extern NSString *MCPAttributeChangedNotification; +extern NSString *MCPRelationChangedNotification; diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.m new file mode 100644 index 00000000..63e580ad --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPEntrepriseNotifications.m @@ -0,0 +1,34 @@ +// +// $Id: MCPEntrepriseNotifications.m 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPEnterpriseNotifications.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +NSString *MCPModelChangedNotification = @"Model has changed"; +NSString *MCPClassDescriptionChangedNotification = @"ClassDescription has changed"; +NSString *MCPAttributeChangedNotification = @"Attribute has changed"; +NSString *MCPRelationChangedNotification = @"Relation has changed"; diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.h new file mode 100644 index 00000000..c3882fee --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.h @@ -0,0 +1,74 @@ +// +// $Id: MCPJoin.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPJoin.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 18/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +@class MCPModel; +@class MCPClassDescription; +@class MCPAttribute; +@class MCPRelation; +@class MCPRelation; + +@interface MCPJoin : NSObject <NSCoding> +{ +@protected + // Note that NONE of these attributes are retained!!! + // Instead all these objects are notified of the existence of the join + // and are responsible to invalidate/delete it if necessary. + MCPRelation *relation; + MCPAttribute *origin; + MCPAttribute *destination; +} + +#pragma mark Class methods ++ (void) initialize; + +#pragma mark Life cycle +- (id) initForRelation:(MCPRelation *) iRelation from:(MCPAttribute *) iOrigin to:(MCPAttribute *) iDestination; +- (void) invalidate; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Setters +// No setter for relation : should be set at init time! +- (void) setOrigin:(MCPAttribute *) iOrigin; +- (void) setDestination:(MCPAttribute *) iDestination; + +#pragma mark Getters +- (MCPRelation *) relation; +- (MCPAttribute *) origin; +- (MCPAttribute *) destination; +- (unsigned int) index; + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.m new file mode 100644 index 00000000..cf202196 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPJoin.m @@ -0,0 +1,183 @@ +// +// $Id: MCPJoin.m 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPJoin.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 18/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPJoin.h" + +#import "MCPModel.h" +#import "MCPClassDescription.h" +#import "MCPAttribute.h" +#import "MCPRelation.h" +#import "MCPRelation.h" + +@implementation MCPJoin + +#pragma mark Class methods ++ (void) initialize +{ + if (self = [MCPJoin class]) { + [self setVersion:010101]; // Ma.Mi.Re -> MaMiRe + } + return; +} + +#pragma mark Life cycle +- (id) initForRelation:(MCPRelation *) iRelation from:(MCPAttribute *) iOrigin to:(MCPAttribute *) iDestination; +{ + self = [super init]; + if (self) { + relation = iRelation; + [self setOrigin:iOrigin]; + [self setDestination:iDestination]; + } + return self; +} + +- (void) invalidate +{ + [self retain]; + NSLog(@"Enterring -[MCPJoin invalidate], retain count is %u (after retaining : should be 4)", [self retainCount]); + [origin removeObjectFromJoinsAtIndex:[origin indexOfJoinIdenticalTo:self]]; + [destination removeObjectFromJoinsAtIndex:[destination indexOfJoinIdenticalTo:self]]; + [relation removeObjectFromJoinsAtIndex:[relation indexOfJoinIdenticalTo:self]]; + NSLog(@"Enterring -[MCPJoin invalidate], retain count is %u (before releasing : should be 1)", [self retainCount]); + [self release]; + return; +} + +- (void) dealloc +{ + // Nothing to release, because the attributes are NOT retained. + [super dealloc]; +} + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + relation = [decoder decodeObjectForKey:@"MCPrelation"]; + [self setOrigin:[decoder decodeObjectForKey:@"MCPorigin"]]; + [self setDestination:[decoder decodeObjectForKey:@"MCPdestination"]]; + } + else { + NSLog(@"For some reason, unable to decode MCPJoin from the coder!!!"); + } + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPJoin -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } + [encoder encodeObject:[self relation] forKey:@"MCPrelation"]; + [encoder encodeObject:[self origin] forKey:@"MCPorigin"]; + [encoder encodeObject:[self destination] forKey:@"MCPdestination"]; +} + +#pragma mark Setters +- (void) setOrigin:(MCPAttribute *) iOrigin +{ + if (origin != iOrigin) { + if (origin) { + [origin removeObjectFromJoinsAtIndex:[origin indexOfJoinIdenticalTo:self]]; + } + origin = iOrigin; + if (origin) { + [origin insertObject:self inJoinsAtIndex:[origin countOfJoins]]; + } + } +} + +- (void) setDestination:(MCPAttribute *) iDestination +{ + if (destination != iDestination) { + if (destination) { + [destination removeObjectFromJoinsAtIndex:[destination indexOfJoinIdenticalTo:self]]; + } + destination = iDestination; + if (destination) { + [destination insertObject:self inJoinsAtIndex:[destination countOfJoins]]; + } + } +} + +#pragma mark Getters +- (MCPRelation *) relation +{ + return relation; +} + +- (MCPAttribute *) origin +{ + return origin; +} + +- (MCPAttribute *) destination +{ + return destination; +} + +- (unsigned int) index +{ + return [relation indexOfJoinIdenticalTo:self]; +} + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject +{ + if ([iObject isKindOfClass:[MCPJoin class]]) { + MCPJoin *theJoin = (MCPJoin *)iObject; + + return ([relation isEqual:[theJoin relation]]) && ([origin isEqual:[theJoin origin]]) && ([destination isEqual:[theJoin destination]]); + } + if ([iObject isKindOfClass:[NSDictionary class]]) { + NSDictionary *theDict = (NSDictionary *)iObject; + + return ([relation isEqual:[theDict valueForKey:@"relation"]]) && ([origin isEqual:[theDict valueForKey:@"origin"]]) && ([destination isEqual:[theDict valueForKey:@"destination"]]); + } + return NO; +} + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + + return self; +} + +- (void) release +{ + [super release]; + + return; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.h new file mode 100644 index 00000000..cc307752 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.h @@ -0,0 +1,35 @@ +// +// $Id: MCPModel+MCPEntreprise.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPModel+MCPEntreprise.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 01/11/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 <http://code.google.com/p/sequel-pro/> + +#import "MCPModel.h" + +@interface MCPModel (MCPEntreprise) + +- (void) registerAsClassDescriptionServer; +- (void) registerDescriptionForClass:(NSNotification *) notification; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.m new file mode 100644 index 00000000..cb9ebbaa --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel+MCPEntreprise.m @@ -0,0 +1,57 @@ +// +// $Id: MCPModel+MCPEntreprise.m 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPModel+MCPEntreprise.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 01/11/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPModel+MCPEntreprise.h" + +#import "MCPObject.h" + +@implementation MCPModel (MCPEntreprise) + +#pragma mark Work as a class description server + +- (void) registerAsClassDescriptionServer +{ + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registerDescriptionForClass:) name:NSClassDescriptionNeededForClassNotification object:nil]; +} + +- (void) registerDescriptionForClass:(NSNotification *) notification +{ + Class theClass = [notification object]; + + if ([theClass isSubclassOfClass:[MCPObject class]]) { + + NSString *theClassName = NSStringFromClass(theClass); + unsigned int index = [self indexOfClassDescription:theClassName]; + + if (NSNotFound != index) { + [NSClassDescription registerClassDescription:(NSClassDescription *)[self objectInClassDescriptionsAtIndex:index] forClass:theClass]; + } + } +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.h new file mode 100644 index 00000000..200b4a4b --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.h @@ -0,0 +1,84 @@ +// +// $Id: MCPModel.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPModel.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +@class MCPClassDescription; +@class MCPAttribute; +@class MCPRelation; + +@interface MCPModel : NSObject <NSCoding> +{ +@protected + NSString *name; // Name of the model ... useless. + NSMutableArray *classDescriptions; // Order of the class descriptions in the model. + BOOL usesInnoDBTables; // The database should use InnoDB tables. + // Might add a string holding définition of tables. + // Might also add some sort of template for generated files (at least the header). +} + +#pragma mark Class methods ++ (void) initialize; + +#pragma mark Life cycle +- (id) initWithName:(NSString *) iName; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Making new class description +- (MCPClassDescription *) addNewClassDescriptionWithName:(NSString *) iName inPosition:(int) index; + +#pragma mark Setters +- (void) setName:(NSString *) iName; +- (void) setClassDescriptions:(NSArray *) iClassDescriptions; +- (void) insertObject:(MCPClassDescription *) iClassDescription inClassDescriptionsAtIndex:(unsigned int) index; +- (void) removeObjectFromClassDescriptionsAtIndex:(unsigned int) index; +- (void) setUsesInnoDBTables:(BOOL) iUsesInnoDB; + +// Deprecated : non KVC +//- (void) removeClassDescription:(MCPClassDescription *) iClassDescription; +//- (void) addClassDescription:(MCPClassDescription *) iClassDescription; + +#pragma mark Getters +- (NSString *) name; +- (NSArray *) classDescriptions; +- (unsigned int) countOfClassDescriptions; +- (MCPClassDescription *) objectInClassDescriptionsAtIndex:(unsigned int) index; +- (unsigned int) indexOfClassDescription:(id) iClassDescription; +- (BOOL) usesInnoDBTables; + +// Deprecated : non KVC +//- (MCPClassDescription *) classDescriptionWithClassName:(NSString *) iClassDescriptionClassName; + +#pragma mark Output for logging +- (NSString *) descriptionWithLocale:(NSDictionary *) locale; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.m new file mode 100644 index 00000000..8f9393f2 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPModel.m @@ -0,0 +1,230 @@ +// +// $Id: MCPModel.m 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPModel.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 09/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPModel.h" + +#import "MCPEntrepriseNotifications.h" + +#import "MCPClassDescription.h" +#import "MCPAttribute.h" +#import "MCPRelation.h" + +@implementation MCPModel + +#pragma mark Class methods ++ (void) initialize +{ + if (self = [MCPModel class]) { + [self setVersion:010101]; // Ma.Mi.Re -> MaMiRe + } + return; +} + +#pragma mark Life cycle +- (id) initWithName:(NSString *) iName +{ + self = [super init]; + if (self) { + [self setName:iName]; + classDescriptions = [[NSMutableArray alloc] init]; + } + + return self; +} + +- (void) dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [name release]; + [classDescriptions release]; + [super dealloc]; +} + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder +{ + self = [super init]; + if ((self) && ([decoder allowsKeyedCoding])) { + [self setName:[decoder decodeObjectForKey:@"MCPname"]]; + [self setClassDescriptions:[decoder decodeObjectForKey:@"MCPclassDescriptions"]]; + [self setUsesInnoDBTables:[decoder decodeBoolForKey:@"MCPusesInnoDBTables"]]; + } + else { + NSLog(@"For some reason, unable to decode MCPModel from the coder!!!"); + } + + return self; +} + +- (void) encodeWithCoder:(NSCoder *) encoder +{ + if (! [encoder allowsKeyedCoding]) { + NSLog(@"In MCPModel -encodeWithCoder : Unable to encode to a non-keyed encoder!!, will not perform encoding!!"); + return; + } +// [super encodeWithCoder:encoder]; + [encoder encodeObject:[self name] forKey:@"MCPname"]; + [encoder encodeObject:[self classDescriptions] forKey:@"MCPclassDescriptions"]; + [encoder encodeBool:[self usesInnoDBTables] forKey:@"MCPusesInnoDBTables"]; + [encoder encodeObject:@"1.1.1" forKey:@"MCPversion"]; + return; +} + +#pragma mark Making new class description +- (MCPClassDescription *) addNewClassDescriptionWithName:(NSString *) iName inPosition:(int) index; +{ + MCPClassDescription *theClassDescription = [[MCPClassDescription alloc] initInModel:self withName:iName]; + +// [self addClassDescription:theClassDescription]; + [self insertObject:theClassDescription inClassDescriptionsAtIndex:(index < 0) ? ([self countOfClassDescriptions] + index + 1) : index]; + [theClassDescription release]; + return theClassDescription; +} + +#pragma mark Setters +- (void) setName:(NSString *) iName +{ + if (iName != name) { + [name release]; + name = [iName retain]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; + } +} + +- (void) setClassDescriptions:(NSArray *) iClassDescriptions +{ + if (iClassDescriptions != classDescriptions) { + [classDescriptions release]; + classDescriptions = [[NSMutableArray alloc] initWithArray:iClassDescriptions]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; + } +} + +- (void) insertObject:(MCPClassDescription *) iClassDescription inClassDescriptionsAtIndex:(unsigned int) index +{ + [classDescriptions insertObject:iClassDescription atIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; +} + +- (void) removeObjectFromClassDescriptionsAtIndex:(unsigned int) index +{ + [classDescriptions removeObjectAtIndex:index]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; +} + +- (void) setUsesInnoDBTables:(BOOL) iUsesInnoDB +{ + usesInnoDBTables = iUsesInnoDB; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; +} + +// Deprecated : non KVC +/* +- (void) removeClassDescription:(MCPClassDescription *) iClassDescription +{ + [classDescriptions removeObject:iClassDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; +} + + - (void) addClassDescription:(MCPClassDescription *) iClassDescription + { + [classDescriptions addObject:iClassDescription]; + [[NSNotificationCenter defaultCenter] postNotificationName:MCPModelChangedNotification object:self]; + } + + */ + +#pragma mark Getters +- (NSString *) name +{ + return name; +// return [NSString stringWithString:name]; +} + +- (NSArray *) classDescriptions +{ + return [NSArray arrayWithArray:classDescriptions]; +} + +- (unsigned int) countOfClassDescriptions +{ + return [classDescriptions count]; +} + +- (MCPClassDescription *) objectInClassDescriptionsAtIndex:(unsigned int) index +{ + return (MCPClassDescription *)((NSNotFound != index) ? [classDescriptions objectAtIndex:index] : nil); +} + +- (MCPClassDescription *) classDescriptionWithClassName:(NSString *) iClassDescriptionClassName +{ +// Given the implementation of isEqual: for the MCPClassDescription, one should be able to use NSArray method directly: + /* unsigned int i; + + for (i=0; ([classDescriptions count] != i) && (! [iClassDescriptionClassName isEqualToString:[(MCPClassDescription *) [classDescriptions objectAtIndex:i] className]]); ++i ) { + } + return (i == [classDescriptions count]) ? nil : (MCPClassDescription *)[classDescriptions objectAtIndex:i]; + */ + unsigned int theIndex = [classDescriptions indexOfObject:iClassDescriptionClassName]; + return (NSNotFound == theIndex) ? nil : [classDescriptions objectAtIndex:theIndex]; +} + +- (unsigned int) indexOfClassDescription:(id) iClassDescription +{ + return [classDescriptions indexOfObject:iClassDescription]; +} + +- (BOOL) usesInnoDBTables +{ + return usesInnoDBTables; +} + +// Deprecated : non KVC + +#pragma mark Output for logging +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + return [NSString stringWithFormat:@"<MCPModel with name %@ : %p>", [self name], self]; +} + +#pragma mark For debugging the retain counting +- (id) retain +{ + [super retain]; + + return self; +} + +- (void) release +{ + [super release]; + + return; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.h new file mode 100644 index 00000000..7e6aa3b3 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.h @@ -0,0 +1,126 @@ +// +// $Id: MCPObject.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPObject.h +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 19/05/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +/*" Possible return code on some operations of the database interaction. "*/ +typedef enum { + MCPDBReturnUnknown = 0, /*"Unknown state, should not happen."*/ + MCPDBReturnDeleted = 1, /*"The entry have been successfuly deleted from DB."*/ + MCPDBReturnUsed = 2, /*"The entry can not be removed, because some entries are still connected to it (some delete restrict/inhibit delete)."*/ + MCPDBReturnNone = 3, /*"No entry exist with this Id."*/ + MCPDBReturnNew = 4, /*"The entry was indeed new and inserted in the database."*/ + MCPDBReturnUpdated = 5, /*"The entry was updated in the DB."*/ + MCPDBReturnIncompleteKey = 6, /*"Part of the primary key is missing, action not taken."*/ + MCPDBReturnMultiple = 7, /*"Multiple rows are found with a query supposed to return at most one row."*/ + MCPDBReturnNoIdentity = 8, /*"The object does not have attributes that defines identity."*/ + MCPDBReturnNoKey = 9, /*"There is no primary key defined for this entity."*/ + MCPDBReturnNoConnection = 10, /*"The MCPObject is not having a connection."*/ + MCPDBReturnWrongRelationOrigin = 11, /*"Looking for a relation not which origin is not of the specified class."*/ + MCPDBReturnWrongRelationCardinality = 12, /*"Using a method assuming a cardinality of the relation while the relation as the other one."*/ + MCPDBReturnNoSuchRelation = 13, /*"There is no relation with such a name starting from this class."*/ + MCPDBReturnNotTarget = 14, /*"Tried to remove an object from a relation, while the objects does NOT belong to the relation."*/ + MCPDBReturnOK = 100 /*"Everything went OK."*/ +} MCPDBReturnCode; + +@class MCPConnection; +@class MCPClassDescription; +@class MCPRelation; + +@interface MCPObject : NSObject { + MCPClassDescription *classDescription; + MCPConnection *connection; +} + +#pragma mark Life of the Object +/*" Life of the object "*/ +- (id) init; +- (id) initWithDictionary:(NSDictionary *) dictionary; + +- (void) dealloc; + +- (void) setAttributesToDefault; + +#pragma mark Accessors +/*" Accessor(s) "*/ +- (MCPClassDescription *) classDescription; +- (MCPConnection *) connection; + +- (void) setConnection:(MCPConnection *) iConnection; + +#pragma mark Database interface +/*" Database interface "*/ +- (id) readFromDBRow:(NSDictionary *) iDictionary withTableName:(NSString *) iTableName; +- (MCPDBReturnCode) setPrimaryKey:(id) iDictionary andFetchFromDB:(MCPConnection *) iConnection; +//- (MCPDBReturnCode) setPrimaryKey:(NSDictionary *) iDictionary andFetchFromDB:(MCPConnection *) iConnection; +- (NSDictionary *) checkDBId; // the returned dictionary contains a MCPDBReturnCode key with the return code. +- (NSDictionary *) saveInDB; // the returned dictionary contains a MCPDBReturnCode key with the return code. +- (MCPDBReturnCode) getAutoGenerated; +- (MCPDBReturnCode) updateInDB; +- (MCPDBReturnCode) deleteInDB; ++ (MCPDBReturnCode) deleteInDBUsingConnection:(MCPConnection *) iConnection withId:(id) iId; + +#pragma mark Handling relations +/*" Handling realtions "*/ +- (id) getTargetOfRelation:(MCPRelation *) iRelation; +- (id) getTargetOfRelationNamed:(NSString *) iRelationName; +- (MCPDBReturnCode) setTarget:(id) iTarget forRelation:(MCPRelation *) iRelation; +- (MCPDBReturnCode) setTarget:(id) iTarget forRelationNamed:(NSString *) iRelationName; +- (unsigned int) countTargetForRelation:(MCPRelation *) iRelation; +- (unsigned int) countTargetForRelationNamed:(NSString *) iRelationName; +- (MCPObject *) getTargetOfRelation:(MCPRelation *) iRelation atIndex:(unsigned int) iIndex; +- (MCPObject *) getTargetOfRelationNamed:(NSString *) iRelationName atIndex:(unsigned int) iIndex; +- (MCPDBReturnCode) addTarget:(MCPObject *) iTarget toRelation:(MCPRelation *) iRelation; +- (MCPDBReturnCode) addTarget:(MCPObject *) iTarget toRelationNamed:(NSString *) iRelationName; +- (MCPDBReturnCode) removeTarget:(MCPObject *) iTarget toRelation:(MCPRelation *) iRelation; +- (MCPDBReturnCode) removeTarget:(MCPObject *) iTarget toRelationNamed:(NSString *) iRelationName; +- (MCPDBReturnCode) removeTargetToRelation:(MCPRelation *) iRelation atIndex:(unsigned int) iIndex; +- (MCPDBReturnCode) removeTargetToRelationNamed:(NSString *) iRelationName atIndex:(unsigned int) iIndex; +- (unsigned int) indexOfTarget:(MCPObject *) iTarget inRelation:(MCPRelation *) iRelation; +- (unsigned int) indexOfTarget:(MCPObject *) iTarget inRelationNamed:(NSString *) iRelationName; + +#pragma mark Utilities +/*" Utility methods "*/ +- (id) defaultValueForKey:(NSString *) iKey; +- (NSDictionary *) primaryKey; + +/*" Testing equality (VERY important for relation management)"*/ +- (BOOL) isEqual:(id) iObject; + +#pragma mark Output +/*" Output : "*/ +- (NSString *) description; +- (NSString *) descriptionWithLocale:(NSDictionary *) locale; + +#pragma mark Ordering the array for relations +- (NSString *) orderSQLForClassDescription:(MCPClassDescription *) iClassDescription; + +/*" Anti-crash method... "*/ +- (void) setNilValueForKey:(NSString *) iKey; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m new file mode 100644 index 00000000..d3145039 --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPObject.m @@ -0,0 +1,1337 @@ +// +// $Id: MCPObject.m 927 2009-06-24 10:53:07Z stuart02 $ +// +// MCPObject.m +// MCPKit +// +// Created by Serge Cohen (serge.cohen@m4x.org) on 19/05/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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPObject.h" + +#import <MCPKit/MCPKit.h> + +#import "MCPClassDescription.h" +#import "MCPClassDescription+MCPEntreprise.h" +#import "MCPAttribute.h" +#import "MCPRelation.h" +#import "MCPJoin.h" + +@implementation MCPObject + +#pragma mark Life of the object +- (id) init +/*" Taking care of getting the class description for self (after passing the message up). "*/ +{ +// NSArray theAttributes; +// unsigned int i; + + if (self = [super init]) { + classDescription = [[NSClassDescription classDescriptionForClass:[self class]] retain]; + [self setAttributesToDefault]; +/* + theAttributes = [classDescription attributeKeys]; + for (i=0; [theAttributes count] != i; ++i) { // setting the attributtes with proper defaults. + NSString *theKey = (NSString *) [theAttributes objectAtIndex:i]; + + [self setValue:[self defaultValueForKey:theKey] forKey:theKey]; + } + */ + } + return self; +} + +- (id) initWithDictionary:(NSDictionary *) dictionary +/*" This method will use the class description to fetch in the dictionary the values of the attributes of the object.... + Should try to get this description a bit clearer."*/ +{ + self = [super init]; + if (self) { + unsigned int i; + NSArray *attrArray; + + classDescription = [[NSClassDescription classDescriptionForClass:[self class]] retain]; + [self setAttributesToDefault]; + attrArray = [classDescription attributes]; + for (i=0; [attrArray count] != i; ++i) { + MCPAttribute *currentAttribute = (MCPAttribute *)[attrArray objectAtIndex:i]; + id currentValue = [dictionary objectForKey:[currentAttribute name]]; + + if (! currentValue) { + currentValue = [dictionary objectForKey:[currentAttribute externalName]]; + } + if (currentValue) { + [self setValue:currentValue forKey:[currentAttribute name]]; + } + } + } + return self; +} + +- (void) dealloc +/*" Deallocating the class description, then passes the message to super. "*/ +{ +// unsigned int i; +// NSArray *tmpAttributes = [classDescription attributes]; + +/* for (i=0; [tmpAttributes count] != i; ++i) { + MCPAttribute *tmpAttr = [tmpAttributes objectAtIndex:i]; + if ([tmpAttr valueClass]) { + [self setValue:nil forKey:[tmpAttr name]]; + } + } +*/ + [classDescription release]; + [connection release]; + [super dealloc]; +} + +- (void) setAttributesToDefault +/*" Set all the attributes to default values, except for auto-generated and primary key attributes, which are set to NULL. + + NOTE : !! In the current version the auto-generated and key are ALSO set to default values!!. + "*/ +{ + NSArray *theAttributes = [classDescription attributes]; +// NSArray *thePrimKeys = [classDescription primaryKeyAttributes]; + unsigned i; + + for (i=0; [theAttributes count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theAttributes objectAtIndex:i]; + NSString *theKey = [theAttribute name]; + + if (! [theAttribute autoGenerated]) { + [self setValue:[self defaultValueForKey:theKey] forKey:theKey]; + } + else { // Auto-generated attribute ... set it to NULL: + [self setValue:[self defaultValueForKey:theKey] forKey:theKey]; + } + } +/* + for (i=0; [thePrimKeys count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [thePrimKeys objectAtIndex:i]; + if (! [theAttribute autoGenerated]) { + NSString *theKey = [theAttribute name]; + + [[self valueForKey:theKey] release]; + [self setValue:NULL forKey:theKey]; + } + } + */ + return; +} + +#pragma mark Accessor(s) +- (MCPClassDescription *) classDescription +{ + return classDescription; +} + +- (MCPConnection *) connection +{ + if ((! connection) || (! [connection checkConnection])) { + [self setConnection:nil]; + } + return connection; +} + +- (void) setConnection:(MCPConnection *) iConnection +{ + if (iConnection != connection) { + [connection release]; + connection = [iConnection retain]; + } +} + + + +#pragma mark Database interface +- (id) readFromDBRow:(NSDictionary *) iDictionary withTableName:(NSString *) iTableName +/*" Uses a query result row (described as a NSDictionary) to set the instance variables of self. If + the result contains columns from multiple tables, the iTableName can be used to specify the alias + used for the table name corresponding to the class. + +If iTableName == nil, the columns will be searched first without table name (column_name) and if + not found then with the table name in front (from the class description : table_name.column_name). + +Otherwise, the search will be performed in the following order : iTableName.column_name, column_name, + table_name.column_name. + "*/ +{ + NSArray *theAttributeKeys = [classDescription attributeKeys]; + NSArray *thePrefixArray; + unsigned i; + +// Depending on the value of iTableName, get the search order. + if ((nil == iTableName) || ([@"" isEqualToString:iTableName])) { + thePrefixArray = [NSArray arrayWithObjects:@"", [NSMutableString stringWithFormat:@"%@.", [classDescription externalName]], nil]; + } + else { + thePrefixArray = [NSArray arrayWithObjects:[NSString stringWithFormat:@"%@.", iTableName], [NSMutableString stringWithFormat:@"%@.", [classDescription externalName]], @"", nil]; + } + for (i=0; [theAttributeKeys count] != i; ++i) { + id theValue = nil; + MCPAttribute *theAttribute = [classDescription attributeWithName:[theAttributeKeys objectAtIndex:i]]; + unsigned j; + + for (j=0; [thePrefixArray count] != j; ++j) { + if (theValue = [iDictionary objectForKey:[NSString stringWithFormat:@"%@%@", [thePrefixArray objectAtIndex:j], [theAttribute externalName]]]) { + break; + } + } + if (theValue) { + [self takeValue:theValue forKey:[theAttribute name]]; + } + } + return self; +} + + +//- (MCPDBReturnCode) setPrimaryKey:(NSDictionary *) iDictionary andFetchFromDB:(MCPConnection *) iConnection +- (MCPDBReturnCode) setPrimaryKey:(id) iDictionary andFetchFromDB:(MCPConnection *) iConnection +/*" This method is used to retrieve an object from the DB given its precise primary key. It will return self. + If the object is not found in the DB, then all instance variable are set to the default + (and autogenerated/primary-key attributes are set to null)."*/ +{ + BOOL missingKey = NO; + NSArray *theKeyAttr = [classDescription primaryKeyAttributes]; + unsigned i; + NSMutableString *query = [NSMutableString stringWithFormat:@"SELECT * FROM %@ WHERE ", [classDescription externalName]]; + MCPResult *result; + NSDictionary *row; + + [self setConnection:iConnection]; + if (! iConnection) { + return MCPDBReturnNoConnection; + } + for (i=0; [theKeyAttr count] != i; ++i) { + MCPAttribute *theAttr = [classDescription attributeWithName:[(MCPAttribute *)[theKeyAttr objectAtIndex:i] name]]; + id theKeyValue; + +// if (theKeyValue = [iDictionary objectForKey:[theAttr name]]) { + if (theKeyValue = [iDictionary valueForKey:[theAttr name]]) { + if (i != 0) { + [query appendString:@" and "]; + } +// Implies the iDictionary IS a dictionary: +// [query appendFormat:@"(%@ = %@)", [theAttr externalName], [iConnection quoteObject:[iDictionary objectForKey:[theAttr name]]]]; +// If the iDictionary is just an object complying with NSValueCodeing: + [query appendFormat:@"(%@ = %@)", [theAttr externalName], [iConnection quoteObject:theKeyValue]]; + } + else { // Part of the primary key is missing... look for the DB name of the attribute + if (theKeyValue = [iDictionary valueForKey:[theAttr externalName]]) { + if (i != 0) { + [query appendString:@" and "]; + } + [query appendFormat:@"(%@ = %@)", [theAttr externalName], [iConnection quoteObject:theKeyValue]]; + } + else { // Not able to find the value for this attribute !!! + missingKey = YES; + NSLog(@"Unable to find the value for attribute %@ of object of class %@, will make it default", [theAttr name], [self className]); + break; + } + } + } // Now the query is prepared... or a key part is missing: + if (missingKey) { + [self setAttributesToDefault]; + return MCPDBReturnIncompleteKey; + } + result = [iConnection queryString:query]; + if ([result numOfRows] == 0) { + [self setAttributesToDefault]; + return MCPDBReturnNone; + } + row = [result fetchRowAsDictionary]; + [self readFromDBRow:row withTableName:@""]; + if ([result numOfRows] != 1) { + NSLog(@"Got more than one row when querying : %@.... will take only the first one!!! that an IMPORTANT flaw in your data model!!!", query); + return MCPDBReturnMultiple; + } + return MCPDBReturnOK; +} + + +- (NSDictionary *) checkDBId +/*" Using the identity properties of the class, this method will check if self already exists in the DB, + in which case it will set the primary key attributes to match the DB entry. If the object is not present + in the DB, the primary key attributes are set to null if they are declared as aut-generated (untouched otherwise). + +The returned dictionary contains the values of the primary key attributes. It also contains one entry with + key MCPDBReturnCode, which contains the result of the operation (was the object in DB?). + +If the identityAttributes of the class description is empty, the entry will always be considered not to be + in the DB, AND the primary key attributes of the object will be left unchnaged and returned as they are at + call time."*/ +{ + NSArray *theIdAttr = [classDescription identityAttributes]; + NSMutableDictionary *theKeys = [NSMutableDictionary dictionary]; + NSArray *theKeyAttr = [classDescription primaryKeyAttributes]; + MCPConnection *theConnection = [self connection]; + MCPResult *theResult; + unsigned int i; + + if (! theConnection) { + return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:MCPDBReturnNoConnection], @"MCPDBReturnCode"]; + } + if (! [theKeyAttr count]) { // There is no primary key for this object. + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnNoKey] forKey:@"MCPDBReturnCode"]; + return [NSDictionary dictionaryWithDictionary:theKeys]; + } + if (! [theIdAttr count]) { // Identity is not defined for this object. + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnNoIdentity] forKey:@"MCPDBReturnCode"]; + for (i=0; [theKeyAttr count] != i; ++i) { + NSString *theKey = [(MCPAttribute*)[theKeyAttr objectAtIndex:i] name]; + [theKeys setObject:[self valueForKey:theKey] forKey:theKey]; + } + return [NSDictionary dictionaryWithDictionary:theKeys]; + } +// Do the fetch in the DB. + NSMutableString *theQuery = [NSMutableString stringWithString:@"SELECT "]; + + for (i = 0; [theKeyAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[(MCPAttribute *)[theKeyAttr objectAtIndex:i] externalName]]; + } + [theQuery appendFormat:@" FROM %@ WHERE ", [classDescription externalName]]; + for (i = 0; [theIdAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@" AND "]; + } +/* + if ([[(MCPAttribute *)[theIdAttr objectAtIndex:i] valueClass] isSubclassOfClass:[NSString class]]) { + if ([(MCPAttribute *)[theIdAttr objectAtIndex:i] width] != 0) { + [theQuery appendFormat:@"(BINARY %@ = SUBSTRING(%@ FROM 1 FOR %u)", [(MCPAttribute *)[theIdAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theIdAttr objectAtIndex:i] name]]], [(MCPAttribute *)[theIdAttr objectAtIndex:i] width]]; + } + else { + [theQuery appendFormat:@"(BINARY %@ = %@)", [(MCPAttribute *)[theIdAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theIdAttr objectAtIndex:i] name]]]]; + } + } + */ + if (([[(MCPAttribute *)[theIdAttr objectAtIndex:i] valueClass] isSubclassOfClass:[NSString class]]) && ([(MCPAttribute *)[theIdAttr objectAtIndex:i] width] != 0)) { + [theQuery appendFormat:@"(%@ = SUBSTRING(%@ FROM 1 FOR %u))", [(MCPAttribute *)[theIdAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theIdAttr objectAtIndex:i] name]]], [(MCPAttribute *)[theIdAttr objectAtIndex:i] width]]; + } + else { + [theQuery appendFormat:@"(%@ = %@)", [(MCPAttribute *)[theIdAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theIdAttr objectAtIndex:i] name]]]]; + } + } + [theQuery appendString:@" ORDER BY "]; + for (i = 0; [theKeyAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[(MCPAttribute *)[theKeyAttr objectAtIndex:i] externalName]]; + } + theResult = [theConnection queryString:theQuery]; + if ([theResult numOfRows]) { // the object was found. + NSDictionary *theFirstRow = [theResult fetchRowAsDictionary]; + if ([theResult numOfRows] != 1) { + NSLog(@"in MCPObject -checkDBIdWithConnection: method.... not only one (as expected) but %i results were found, will take the first one."); + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnMultiple] forKey:@"MCPDBReturnCode"]; + } + else { + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnOK] forKey:@"MCPDBReturnCode"]; + } + [theKeys addEntriesFromDictionary:theFirstRow]; +// Setting the value of self for the primary key to the one just found. + for (i=0; [theKeyAttr count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theKeyAttr objectAtIndex:i]; + + [self setValue:[theFirstRow objectForKey:[theAttribute externalName]] forKey:[theAttribute name]]; + } + } + else { // Object not found in the DB. + for (i = 0; [theKeyAttr count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *)[theKeyAttr objectAtIndex:i]; + + if ([theAttribute autoGenerated]) { + [self setValue:[self defaultValueForKey:[theAttribute name]] forKey:[theAttribute name]]; + } + [theKeys setObject:[self valueForKey:[theAttribute name]] forKey:[theAttribute name]]; + } + [theKeys setObject:[NSNumber numberWithInt:MCPDBReturnNone] forKey:@"MCPDBReturnCode"]; +// Setting the value of self for the primary key to the default values. +/* + for (i=0; [theKeyAttr count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theKeyAttr objectAtIndex:i]; + + [self setValue:[self defaultValueForKey:[theAttribute name]] forKey:[theAttribute name]]; + } +*/ + } + return [NSDictionary dictionaryWithDictionary:theKeys]; +} + + +- (NSDictionary *) saveInDB +/*" First will use the checkDBIdWithConnection method to check if the entry is already in the database. If this is the case, + it will use the updateInDB... method to update the current DB instance. Otherwise it will make an insert into the database + to save the entry, than it makes a select to get the value of autogenerated column, and use them (if necessary) to return + the primary key (as NSDictionary). + + As for checkDBId... the returned dictionary also have a MCPDBReturnCode key with a NSNumber as value containing the + return code of the operation."*/ +{ + MCPConnection *theConnection = [self connection]; + NSDictionary *theCheckReturn = [self checkDBId]; + NSMutableDictionary *theRet; + int theCheckCode = [(NSNumber *)[theCheckReturn objectForKey:@"MCPDBReturnCode"] intValue]; + unsigned i; + unsigned j; + NSMutableString *theQuery = [NSMutableString string]; + NSArray *theAttr = [classDescription attributes]; + + switch (theCheckCode) { + case MCPDBReturnNoIdentity : // For this object the identity is not set... so we try to save. + break; + + case MCPDBReturnOK : // Object already in the DB (found using checkDBId... method). + // NSLog(@"in saveUsingConnection: the entry already existed so will update the one with key : %@", theCheckReturn); + theCheckCode = [self updateInDB]; + theRet = [NSMutableDictionary dictionaryWithDictionary:[self primaryKey]]; + [theRet setObject:[NSNumber numberWithInt:theCheckCode] forKey:@"MCPDBReturnCode"]; + return [NSDictionary dictionaryWithDictionary:theRet]; + break; + + case MCPDBReturnMultiple : // Multiple were found, the first one will be updated. + NSLog(@"Multiple entries found with the same identity ... will update the first one."); + theCheckCode = [self updateInDB]; + theRet = [NSMutableDictionary dictionaryWithDictionary:[self primaryKey]]; + [theRet setObject:[NSNumber numberWithInt:theCheckCode] forKey:@"MCPDBReturnCode"]; + return [NSDictionary dictionaryWithDictionary:theRet]; + break; + + case MCPDBReturnNone : // The entry is not already in the DB, save it... + break; + + case MCPDBReturnNoConnection : // Self does not have a connection. + NSLog(@"Can not save when the connection is not working..."); + break; + + default : // We should not arrive here anyway. + NSLog(@"For some resons we got a unexpected result from checkDBId : %i", theCheckCode); + break; + } + +// Generate the INSERT query: + [theQuery appendFormat:@"INSERT INTO %@ (", [classDescription externalName]]; + j = 0; + for (i=0; [theAttr count] != i; ++i) { + if (! [(MCPAttribute *)[theAttr objectAtIndex:i] autoGenerated]) { + if (j) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[(MCPAttribute *)[theAttr objectAtIndex:i] externalName]]; + ++j; + } + } + [theQuery appendString:@") VALUES ("]; + j = 0; + for (i=0; [theAttr count] != i; ++i) { + if (! [(MCPAttribute *)[theAttr objectAtIndex:i] autoGenerated]) { + if (j) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[theConnection quoteObject:[self valueForKey:[(MCPAttribute *)[theAttr objectAtIndex:i] name]]]]; + ++j; + } + } + [theQuery appendString:@")"]; +// Finished preparing the query. + +// Now we perform the query... + [theConnection queryString:theQuery]; + if (1 != (i = [theConnection affectedRows])) { // More than one row affected ... Should NEVER occure. + NSLog(@"Problem while saving a MCPObject to the database : number of inserted rows is : %i !!!", i); + NSLog(@"Maybe there is an error : %@ ", [theConnection getLastErrorMessage]); + NSLog(@"The class of the object is : %@, and it's description is :\n%@", [self className], [self descriptionWithLocale:nil]); + theCheckCode = (i == 0) ? MCPDBReturnNone : MCPDBReturnMultiple; + } + else { + theCheckCode = MCPDBReturnOK; + } + +// Finally we get the primary key of the inserted object: + if ([classDescription singleIntAutoGenKey]) { + NSString *thePrimKey = [(MCPAttribute *) [[classDescription primaryKeyAttributes] objectAtIndex:0] name]; + + [self setValue:[NSNumber numberWithLongLong:[theConnection insertId]] forKey:thePrimKey]; + } + [self getAutoGenerated]; + theRet = [NSMutableDictionary dictionaryWithDictionary:[self primaryKey]]; + [theRet setObject:[NSNumber numberWithInt:theCheckCode] forKey:@"MCPDBReturnCode"]; + + return [NSDictionary dictionaryWithDictionary:theRet]; +} + + + +- (MCPDBReturnCode) getAutoGenerated +/*" This method will use the Identity attributes of the object to retrieve the autogenerated +attributes from the database. + +If the identity is not defined for this class/entity, then it will try to use the primary key +to get the auto-generated values. + +Obviously this might generate a bug if one of the Identity attributes is autogenerated. +This will create a trouble if some object does not have an identity defined but still contains +some auto-generated attributes. +"*/ +{ + NSMutableArray *theAutoAttr = [NSMutableArray array]; + NSArray *theAttr = [classDescription attributes]; + NSArray *theKeyAttr = [classDescription primaryKeyAttributes]; + unsigned i,j; + NSMutableString *theQuery; + int theCheckCode; + MCPResult *theResult; + NSDictionary *theRow; + MCPConnection *theConnection = [self connection]; + + if (! theConnection) { + return MCPDBReturnNoConnection; + } + + if (0 != [[classDescription identityAttributes] count]) { + theCheckCode = [(NSNumber *)[[self checkDBId] objectForKey:@"MCPDBReturnCode"] intValue]; + if ((MCPDBReturnOK != theCheckCode) && (MCPDBReturnMultiple != theCheckCode)) { + NSLog(@"Unable to get the primary key for the object, will abort now the fetch of autoGenerated attributes (left unchanged)!"); + return theCheckCode; + } + if (MCPDBReturnMultiple == theCheckCode) { + NSLog(@"Will get the autoGenerated values for the first entry...."); + } + } + j = 0; + for (i=0; [theAttr count] != i; ++i) { // generate the array with autoGenerated attributes. + if ([(MCPAttribute *)[theAttr objectAtIndex:i] autoGenerated]) { + [theAutoAttr insertObject:[theAttr objectAtIndex:i] atIndex:j]; + ++j; + } + } + if (0 == [theAutoAttr count]) { + return MCPDBReturnOK; + } + +// Make the query: + theQuery = [NSMutableString stringWithString:@"SELECT "]; + for (i=0; [theAutoAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@", "]; + } + [theQuery appendString:[(MCPAttribute *)[theAutoAttr objectAtIndex:i] externalName]]; + } + [theQuery appendFormat:@" FROM %@ WHERE ", [classDescription externalName]]; +// Preparing the Where Clause: + for (i=0; [theKeyAttr count] != i; ++i) { + if (i) { + [theQuery appendString:@" AND "]; + } + [theQuery appendFormat:@"( %@ = %@ )", [(MCPAttribute*)[theKeyAttr objectAtIndex:i] externalName], [theConnection quoteObject:[self valueForKey:[(MCPAttribute*)[theKeyAttr objectAtIndex:i] name]]]]; + } +// Query is ready: + theResult = [theConnection queryString:theQuery]; + if (! theResult) { + NSLog(@"While fetching the auto-generated part of the object, got an error -a nil MCPResult-. Will report that as a MCPDBReturnNone."); + return MCPDBReturnNone; + } + if (0 == [theResult numOfRows]) { // The entry was not found in the DB. + if (0 == [[classDescription identityAttributes] count]) { + NSLog(@"It seems that the object is not in the DB... but the identity is not defined so there might be a trouble with having the proper ID."); + } + return MCPDBReturnNone; + } +// Getting the values from the DB select: + theRow = [theResult fetchRowAsDictionary]; + for (i=0; [theAutoAttr count] != i; ++i) { + [self setValue:[theRow objectForKey:[(MCPAttribute*)[theAutoAttr objectAtIndex:i] externalName]] forKey:[(MCPAttribute*)[theAutoAttr objectAtIndex:i] name]]; + } +// Returning the proper value: + if (1 != [theResult numOfRows]) { + NSLog(@"Multiple entries (or none : %llu), got the values for the first one...", [theResult numOfRows]); + return MCPDBReturnMultiple; + } + return MCPDBReturnOK; +} + + +- (MCPDBReturnCode) updateInDB +/*"This method will use the primary key value held by the object to modify the object that is saved in the DB. + + (NO YET IMPLEMENTED) If the primary key is not complete (some of the attributes are to null or 0) and the identityAttributes + (from class description) is not an empty list then it will perform a checkDBId... to try to retrieve the + missing part of the primary key. + "*/ +{ + NSArray *theAttributes = [classDescription attributes]; + NSArray *thePrimKeyAttributes = [classDescription primaryKeyAttributes]; + NSMutableString *theQuery = [NSMutableString string]; + int theCheckCode; + unsigned i, j; + MCPConnection *theConnection = [self connection]; + + if (! theConnection) { + return MCPDBReturnNoConnection; + } +// Generate the query + [theQuery appendFormat:@"UPDATE %@ SET ", [classDescription externalName]]; + // Prepare the value key pairs: + j=0; + for (i=0 ; [theAttributes count] != i; ++i) { + id theValue = [self valueForKey:[(MCPAttribute *)[theAttributes objectAtIndex:i] name]]; + + if ((theValue) && (! [(MCPAttribute *)[theAttributes objectAtIndex:i] autoGenerated])) { + if (j) { + [theQuery appendString:@", "]; + } + [theQuery appendFormat:@"%@ = %@ ", [(MCPAttribute *)[theAttributes objectAtIndex:i] externalName], [theConnection quoteObject:theValue]]; + ++j; + } + } + if (0 == j) { // Nothing has to be updated in this entry.... + NSLog(@"Tried to update a row from an object that does NOT contain any information!!"); + return MCPDBReturnNone; + } + // Prepare the WHERE clause: + [theQuery appendFormat:@" WHERE "]; + j = 0; + for (i=0; [thePrimKeyAttributes count] != i; ++i) { + id theValue = [self valueForKey:[(MCPAttribute *)[thePrimKeyAttributes objectAtIndex:i] name]]; + + if (j) { + [theQuery appendString:@" AND "]; + } + if (theValue) { + [theQuery appendFormat:@"%@ = %@", [(MCPAttribute *)[thePrimKeyAttributes objectAtIndex:i] externalName], [theConnection quoteObject:theValue]]; + ++j; + } + } +// Perform the update: + if (j == [thePrimKeyAttributes count]) { + [theConnection queryString:theQuery]; + i = [theConnection affectedRows]; + switch (i) { + case 0 : + theCheckCode = MCPDBReturnNone; + break; + case 1 : + theCheckCode = MCPDBReturnOK; + break; + default : + theCheckCode = MCPDBReturnMultiple; + break; + } + } + else { + NSDictionary *theKeyCheck = [self checkDBId]; + int theIdCheckCode = [(NSNumber *) [theKeyCheck objectForKey:@"MCPDBReturnCode"] intValue]; + + if (MCPDBReturnOK == theIdCheckCode) { + theCheckCode = [self updateInDB]; + } + else { + theCheckCode = MCPDBReturnNoKey; + } + } +// Return proper code: + return theCheckCode; +} + + +- (MCPDBReturnCode) deleteInDB +/*" Uses the connection to delete the object from the DB. In this process the autoGenerated attributes are set to null. + If the primary key is not completely set, this method calls checkDBId... method to try to complete it + (if the identityAttributes of the class description is not empty)."*/ +{ + NSArray *theKeyAttributes = [classDescription primaryKeyAttributes]; + NSArray *theAttributes = [classDescription attributes]; + unsigned i; +// NSMutableDictionary *theKeyValue = [NSMutableDictionary dictionary]; + int theCheckCode = MCPDBReturnOK; + MCPConnection *theConnection = [self connection]; + + if (! theConnection) { + return MCPDBReturnNoConnection; + } +// Get the value of the primary key: + for (i=0; [theKeyAttributes count] != i; ++i) { + id theValue = [self valueForKey:[(MCPAttribute*) [theKeyAttributes objectAtIndex:i] name]]; + if ((! theValue) || ([theValue isKindOfClass:[NSNumber class]] && (0 == [theValue intValue]))) { + theCheckCode = MCPDBReturnIncompleteKey; + break; + } + } +// If incomplete, try to find the rest, and perform again: + if (MCPDBReturnIncompleteKey == theCheckCode) { + if (MCPDBReturnOK == [(NSNumber *)[[self checkDBId] objectForKey:@"MCPDBReturnCode"] intValue]) { + return [self deleteInDB]; + } + else { + return MCPDBReturnIncompleteKey; + } + } + +// Perform the deletion from the DB: + theCheckCode = [[self class] deleteInDBUsingConnection:theConnection withId:self]; + +// set the auto-generated attributes to proper values: + for (i=0 ; [theAttributes count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theAttributes objectAtIndex:i]; + if ([theAttribute autoGenerated]) { +// [self setValue:NULL forKey:[theAttribute name]]; + [self setValue:[self defaultValueForKey:[theAttribute name]] forKey:[theAttribute name]]; + } + } + +// Finished... just have to return the proper value: + return theCheckCode; +} + + ++ (MCPDBReturnCode) deleteInDBUsingConnection:(MCPConnection *) iConnection withId:(id) iId +/*" Uses the connection to remove from the DB the entry corresponding to the primary key id given by iId. + If any part of the primary key is missing, nothing is done."*/ +{ + NSMutableString *theQuery = [NSMutableString string]; + MCPClassDescription *theClassDescription = (MCPClassDescription *) [NSClassDescription classDescriptionForClass:[self class]]; + NSArray *theKeyAttributes = [theClassDescription primaryKeyAttributes]; + unsigned i; + + if (! [iConnection checkConnection]) { + return MCPDBReturnNoConnection; + } +// Generate the query: + [theQuery appendFormat:@"DELETE FROM %@ WHERE ", [theClassDescription externalName]]; +// Prepare the WHERE STATEMENT: + for (i=0; [theKeyAttributes count] != i; ++i) { + if (i) { + [theQuery appendString:@" AND "]; + } +// [theQuery appendFormat:@"( %@ = %@ )", [(MCPAttribute*)[theKeyAttributes objectAtIndex:i] externalName], [iConnection quoteObject:[iId objectForKey:[(MCPAttribute*)[theKeyAttributes objectAtIndex:i] name]]]]; + [theQuery appendFormat:@"( %@ = %@ )", [(MCPAttribute*)[theKeyAttributes objectAtIndex:i] externalName], [iConnection quoteObject:[iId valueForKey:[(MCPAttribute*)[theKeyAttributes objectAtIndex:i] name]]]]; + } +// Perform the query: + [iConnection queryString:theQuery]; +// Return the proper code: + i = [iConnection affectedRows]; + + if (1 < i) { + return MCPDBReturnMultiple; + } + if (0 == i) { + return MCPDBReturnNone; + } + return MCPDBReturnOK; +} + + +#pragma mark Handling realtions +- (id) getTargetOfRelation:(MCPRelation *) iRelation +/*" This method is using the information from iRelation to fetch the targe of the relation in the DB. + +If iRelation is flagged as a to-one, then the return type is the type of the target object (value might be nil, if the target was not found). +If iRelation is flagged as a to-many, then the return type is NSArray (and might be empty if no target were found). + +In any case this method is first checking that iRelation is starting from the class of self, and that self is connected to the DB. +Also the returned object is ALWAYS autoreleased before being returned. +"*/ +{ + NSMutableString *query; + MCPClassDescription *destinationDesc; + NSArray *joins; + NSArray *keys; + NSArray *ids; + MCPResult *result; + unsigned int i; + NSDictionary *theRow; + id theRet; + + if (! iRelation) { + NSLog(@"Tried to get the target of a relation... but the relation object is nil"); + } + else { +// NSLog(@"Trying to get the target of the realtion : %@", [iRelation descriptionWithLocale:nil]); + } + if ((! [connection isConnected]) || (! [classDescription isEqual:[iRelation origin]])) { // Error condition. + return nil; + } +// Generating the query: + destinationDesc = [iRelation destination]; + keys = [destinationDesc primaryKeyAttributes]; + joins = [iRelation joins]; + ids = [destinationDesc identityAttributes]; + query = [[NSMutableString alloc] initWithString:@"SELECT "]; + for (i=0; [keys count] != i; ++i) { + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[keys objectAtIndex:i] externalName]]; + } + [query appendFormat:@" FROM %@ WHERE ", [destinationDesc externalName]]; + for (i=0; [joins count] != i; ++i) { + MCPJoin *theJoin = (MCPJoin *)[joins objectAtIndex:i]; + + if (i) { + [query appendString:@" and "]; + } + [query appendFormat:@"(%@ = %@)", [[theJoin destination] externalName], [connection quoteObject:[self valueForKey:[[theJoin origin] name]]]]; + } +// NSLog(@"in -[MCPObject getTargetOfRelation:%@]; query is %@...", [iRelation descriptionWithLocale:nil], query); + [query appendString:[self orderSQLForClassDescription:destinationDesc]]; +// NSLog(@"in -[MCPObject getTargetOfRelation:%@]; query is %@...", [iRelation descriptionWithLocale:nil], query); +/* + [query appendString:@" ORDER BY "]; + for (i=0; [ids count] != i; ++i) { // Generating the order : + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[ids objectAtIndex:i] externalName]]; + } +*/ + result = [connection queryString:query]; + [query release]; + +// Getting the results in proper objects: + if ([iRelation isToMany]) { // To-Many relation + NSMutableArray *theArrayRet = [[NSMutableArray alloc] init]; + + theRet = theArrayRet; + while (theRow = [result fetchRowAsDictionary]) { + MCPObject *theTarget = [[[destinationDesc representedClass] alloc] init]; + + [theTarget setPrimaryKey:theRow andFetchFromDB:connection]; + [theArrayRet insertObject:theTarget atIndex:[theArrayRet count]]; + [theTarget release]; + } + } + else { // To-One relation + theRow = [result fetchRowAsDictionary]; + if (theRow) { + theRet = [[[destinationDesc representedClass] alloc] init]; + [theRet setPrimaryKey:theRow andFetchFromDB:connection]; + } + else { + theRet = nil; + } + } + [theRet autorelease]; + return theRet; +} + +- (id) getTargetOfRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self getTargetOfRelation:theRelation] : nil; +} + +- (MCPDBReturnCode) setTarget:(id) iTarget forRelation:(MCPRelation *) iRelation +/*" This method will modify the DB content so that the corresponding relation is deleted. + If any value corresping to an attribute of self is modified in the DB, then it will update all attributes of self to reflect DB status. + +Obviuosly this method is taking care of only to-one relation. It will hence check that iRelation is a to-one relation starting from self. +Finally, you can use setTarget:nil forRelation:... to 'delete' a previously establlished relation (if the model and DB permit it)."*/ +{ + unsigned int i; + MCPObject *oldTarget; + + if (! [connection isConnected]) { + return MCPDBReturnNoConnection; + } + if (! [classDescription isEqual:[iRelation origin]]) { + return MCPDBReturnWrongRelationOrigin; + } + if ([iRelation isToMany]) { + return MCPDBReturnWrongRelationCardinality; + } + + oldTarget = [self getTargetOfRelation:iRelation]; + if ((oldTarget) && ([iTarget isEqual:oldTarget])) { // No need to change the relation's target. + return MCPDBReturnOK; + } + for (i=0; [iRelation countOfJoins] != i; ++i) { + MCPJoin *theJoin = [iRelation objectInJoinsAtIndex:i]; + + if (([[theJoin origin] isPartOfKey]) && (![[theJoin destination] isPartOfKey])) { // Will change the destination... + [iTarget setValue:[self valueForKey:[[theJoin origin] name]] forKey:[[theJoin destination] name]]; + [oldTarget setValue:[oldTarget defaultValueForKey:[[theJoin destination] name]] forKey:[[theJoin destination] name]]; + } + else { // Will change the origin + [self setValue:[iTarget valueForKey:[[theJoin destination] name]] forKey:[[theJoin origin] name]]; + } + } + if ([iRelation ownsDestination]) { + [oldTarget deleteInDB]; + } + return MCPDBReturnOK; +} + +- (MCPDBReturnCode) setTarget:(id) iTarget forRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self setTarget:iTarget forRelation:theRelation] : MCPDBReturnNoSuchRelation; +} + +- (unsigned int) countTargetForRelation:(MCPRelation *) iRelation +{ + NSMutableString *theQuery; + unsigned int i; + NSArray *theJoinArray; + MCPResult *theResult; + NSDictionary *theRow; + + if ((! [connection isConnected]) || (! [classDescription isEqual:[iRelation origin]])){ + return 0; + } + theJoinArray = [iRelation joins]; + theQuery = [[NSMutableString alloc] initWithFormat:@"SELECT COUNT(*) FROM %@ WHERE ", [[iRelation destination] externalName]]; + for (i=0; [theJoinArray count] != i; ++i) { + MCPJoin *theJoin = (MCPJoin *)[theJoinArray objectAtIndex:i]; + if (i) { + [theQuery appendString:@" AND "]; + } + [theQuery appendFormat:@"( %@ = %@ )", [[theJoin destination] externalName], [connection quoteObject:[self valueForKey:[[theJoin origin] name]]]]; + } + theResult = [connection queryString:theQuery]; + [theQuery release]; + theRow = [theResult fetchRowAsDictionary]; + return [(NSNumber *)[theRow objectForKey:@"COUNT(*)"] unsignedIntValue]; +} + +- (unsigned int) countTargetForRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self countTargetForRelation:theRelation] : 0; +} + + +- (MCPObject *) getTargetOfRelation:(MCPRelation *) iRelation atIndex:(unsigned int) iIndex +/*" This method will return the specific object which is the iIndex'th object of th relation (after ordering the object using the destination identity). + +This method (like other assuming order in the relation targets) will be doubtfull if the class does NOT have a single identity attribute."*/ +{ + NSMutableString *query; + MCPClassDescription *destinationDesc; + NSArray *joins; + NSArray *keys; + NSArray *ids; + MCPResult *result; + unsigned int i; + NSDictionary *theRow; + MCPObject *theRet; + + if (! iRelation) { + NSLog(@"Tried to get the target of a relation... but the relation object is nil"); + } + else { +// NSLog(@"Trying to get the target of the realtion : %@", [iRelation descriptionWithLocale:nil]); + } + if ((! [connection isConnected]) || (! [classDescription isEqual:[iRelation origin]])) { // Error condition. + return nil; + } + if (! [iRelation isToMany]) { + NSLog(@"Tried to use the -[MCPObject getTargetOfRelation:atIndex:] on a to-one relation... this does NOT works!!! You should use -[MCPObject getTargetOfRelation:] instead!!!"); + return [self getTargetOfRelation:iRelation]; + } +// Generating the query: + destinationDesc = [iRelation destination]; + keys = [destinationDesc primaryKeyAttributes]; + joins = [iRelation joins]; + ids = [destinationDesc identityAttributes]; + query = [[NSMutableString alloc] initWithString:@"SELECT "]; + for (i=0; [keys count] != i; ++i) { + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[keys objectAtIndex:i] externalName]]; + } + [query appendFormat:@" FROM %@ WHERE ", [destinationDesc externalName]]; + for (i=0; [joins count] != i; ++i) { + MCPJoin *theJoin = (MCPJoin *)[joins objectAtIndex:i]; + + if (i) { + [query appendString:@" and "]; + } + [query appendFormat:@"(%@ = %@)", [[theJoin destination] externalName], [connection quoteObject:[self valueForKey:[[theJoin origin] name]]]]; + } + [query appendString:[self orderSQLForClassDescription:destinationDesc]]; +/* + [query appendString:@" ORDER BY "]; + for (i=0; [ids count] != i; ++i) { // Generating the order : + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[ids objectAtIndex:i] externalName]]; + } +*/ + [query appendFormat:@" LIMIT 1 OFFSET %u", iIndex]; + result = [connection queryString:query]; + [query release]; + +// Getting the results in proper objects: + theRow = [result fetchRowAsDictionary]; + if (theRow) { + theRet = (MCPObject *)[[[destinationDesc representedClass] alloc] init]; + [theRet setPrimaryKey:theRow andFetchFromDB:connection]; + [theRet autorelease]; + } + else { + theRet = nil; + } + return theRet; +} + +- (MCPObject *) getTargetOfRelationNamed:(NSString *) iRelationName atIndex:(unsigned int) iIndex +/*"This is the equivalent of the getTargetOfRelation:atIndex:, but giving a relation name instead of the MCPRelation object itself."*/ +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self getTargetOfRelation:theRelation atIndex:iIndex]: nil; +} + +- (MCPDBReturnCode) addTarget:(MCPObject *) iTarget toRelation:(MCPRelation *) iRelation +/*" This method will modify the DB content so that the corresponding relation is added. +If any value corresping to an attribute of self i modified in the DB, then it will update all attributes of self to reflect DB status. + +Obviuosly this method is taking care of only to-many relation. It will hence check that iRelation is a to-many relation starting from self."*/ +{ + NSArray *joins; + unsigned int i; + NSDictionary *saveReturn; + + if (! [connection isConnected]) { + return MCPDBReturnNoConnection; + } + if (! [classDescription isEqual:[iRelation origin]]) { + return MCPDBReturnWrongRelationOrigin; + } + if (! [iRelation isToMany]) { + return MCPDBReturnWrongRelationCardinality; + } + joins = [iRelation joins]; + for (i=0; [joins count] != i; ++i) { // Will change only values of iTarget, because it is a to-many relation. + MCPJoin *join = (MCPJoin *)[joins objectAtIndex:i]; + + [iTarget setValue:[self valueForKey:[[join origin] name]] forKey:[[join destination] name]]; + } + if (! [[iTarget connection] isConnected]) { + [iTarget setConnection:connection]; + } + saveReturn = [iTarget saveInDB]; + return (MCPDBReturnCode)[(NSNumber *)[saveReturn objectForKey:@"MCPDBReturnCode"] unsignedIntValue]; +} + +- (MCPDBReturnCode) addTarget:(MCPObject *) iTarget toRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self addTarget:iTarget toRelation:theRelation] : MCPDBReturnNoSuchRelation; +} + +- (MCPDBReturnCode) removeTarget:(MCPObject *) iTarget toRelation:(MCPRelation *) iRelation +/*" This method will modify the DB content so that the corresponding relation is removed. +If any value corresping to an attribute of self i modified in the DB, then it will update all attributes of self to reflect DB status. + +Obviuosly this method is taking care of only to-many relation. It will hence check that iRelation is a to-many relation starting from self."*/ +{ + NSArray *joins; + unsigned int i; +// NSDictionary *saveReturn; + BOOL targetIsTarget = YES; + MCPDBReturnCode returnCode; + + if (! [connection isConnected]) { + return MCPDBReturnNoConnection; + } + if (! [classDescription isEqual:[iRelation origin]]) { + return MCPDBReturnWrongRelationOrigin; + } + if (! [iRelation isToMany]) { + return MCPDBReturnWrongRelationCardinality; + } + joins = [iRelation joins]; + for (i=0; [joins count] != i; ++i) { + MCPJoin *join = (MCPJoin *)[joins objectAtIndex:i]; + targetIsTarget = targetIsTarget && [[iTarget valueForKey:[[join destination] name]] isEqual:[self valueForKey:[[join origin] name]]]; + } + if (! targetIsTarget) { + return MCPDBReturnNotTarget; + } + if ([iRelation ownsDestination]) { // just delete the target from the DB. + returnCode = [iTarget deleteInDB]; + } + for (i=0; [joins count] != i; ++i) { // Put all the destination to default... + MCPJoin *join = (MCPJoin *)[joins objectAtIndex:i]; + + [iTarget setValue:[[join destination] defaultValue] forKey:[[join destination] name]]; + } + if (! [iRelation ownsDestination]) { + returnCode = [(NSNumber *)[[iTarget saveInDB] objectForKey:@"MCPDBReturnCode"] unsignedIntValue]; + } + return returnCode; +} + +- (MCPDBReturnCode) removeTarget:(MCPObject *) iTarget toRelationNamed:(NSString *) iRelationName +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self removeTarget:iTarget toRelation:theRelation] : MCPDBReturnNoSuchRelation; +} + +- (MCPDBReturnCode) removeTargetToRelation:(MCPRelation *) iRelation atIndex:(unsigned int) iIndex +/*" This method will use an index t first query the object that it should remove from the relation, then uses the -[MCPObject removeTarget:toRelation:] + method to remove the object from the relation. If the index is out of bound (the returned object is nil), it will return MCPDBReturnNone, to signal + there was no object with this index in the relation."*/ +{ + MCPObject *target; + + if (! [connection isConnected]) { + return MCPDBReturnNoConnection; + } + if (! [classDescription isEqual:[iRelation origin]]) { + return MCPDBReturnWrongRelationOrigin; + } + if (! [iRelation isToMany]) { + return MCPDBReturnWrongRelationCardinality; + } + target = [self getTargetOfRelation:iRelation atIndex:iIndex]; + if (target) { + return [self removeTarget:target toRelation:iRelation]; + } + else { + return MCPDBReturnNone; + } +} + +- (MCPDBReturnCode) removeTargetToRelationNamed:(NSString *) iRelationName atIndex:(unsigned int) iIndex +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self removeTargetToRelation:theRelation atIndex:iIndex] : MCPDBReturnNoSuchRelation; +} + +- (unsigned int) indexOfTarget:(MCPObject *) iTarget inRelation:(MCPRelation *) iRelation +/*" Returns the index of the target object within the relation. Return NSNotFound if the objkect is NOT in the relation!!"*/ +{ + NSMutableString *query; + NSArray *joins; + NSArray *keys; + NSArray *ids; + unsigned int i; + MCPResult *result; + NSDictionary *row; + NSMutableDictionary *targetKey; + BOOL targetIsTarget = YES; + + if ((! [connection isConnected]) || (! [classDescription isEqual:[iRelation origin]]) || (! [iRelation isToMany])) { // Checking the realtion object. + return NSNotFound; + } + query = [[NSMutableString alloc] initWithString:@"SELECT "]; + joins = [iRelation joins]; +// keys = [[iRelation destination] attributeKeys]; + keys = [[iRelation destination] primaryKeyAttributes]; + ids = [[iRelation destination] identityAttributes]; + for (i=0; [keys count] != i; ++i) { + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[keys objectAtIndex:i] externalName]]; + } + [query appendFormat:@"FROM %@ WHERE ", [[iRelation destination] externalName]]; + for (i=0; [joins count] != i; ++i) { + MCPJoin *join = (MCPJoin *)[joins objectAtIndex:i]; + + targetIsTarget = targetIsTarget && [[iTarget valueForKey:[[join destination] name]] isEqual:[self valueForKey:[[join origin] name]]]; + if (i) { + [query appendString:@" AND "]; + } + [query appendFormat:@"( %@ = %@ )", [[join destination] externalName], [connection quoteObject:[self valueForKey:[[join origin] name]]]]; + } + if (! targetIsTarget) { // Checking that iTarget belongs to the relation. + [query release]; + return NSNotFound; + } + [query appendString:[self orderSQLForClassDescription:[iRelation destination]]]; +/* + [query appendString:@" ORDER BY "]; + for (i=0; [ids count] != i; ++i) { + if (i) { + [query appendString:@", "]; + } + [query appendString:[(MCPAttribute *)[ids objectAtIndex:i] externalName]]; + } +*/ + targetKey = [[NSMutableDictionary alloc] init]; + for (i=0; [keys count] != i; ++i) { // Setting the targetKey to the row that should gives the target. + MCPAttribute *attribute = (MCPAttribute *)[keys objectAtIndex:i]; + + [targetKey setObject:[iTarget valueForKey:[attribute name]] forKey:[attribute externalName]]; + } + result = [connection queryString:query]; + [query release]; + i = 0; + while (row = [result fetchRowAsDictionary]) { + if ([targetKey isEqualToDictionary:row]) { // We have found the proper object return i (after cleaning up); + [targetKey release]; + return i; + } + ++i; + } + return NSNotFound; +} + +- (unsigned int) indexOfTarget:(MCPObject *) iTarget inRelationNamed:(NSString *) iRelationName +/*" The equivalent of -[MCPObject indexOfTarget:inRelation:] but using relation name instead of a MCPRelation +object. Indeed after getting the MCPRelation object corresponding to the name, this method will only return +the result of the corresponding call to -[MCPObject indexOfTarget:inRelation:]."*/ +{ + MCPRelation *theRelation = [classDescription relationWithName:iRelationName]; + return (theRelation) ? [self indexOfTarget:iTarget inRelation:theRelation] : NSNotFound; +} + +#pragma mark Utility methods +- (id) defaultValueForKey:(NSString *) iKey +/*" This method will return the default value (object) for the given key."*/ +{ + MCPAttribute *theAttribute = [classDescription attributeWithName:iKey]; + + if ([theAttribute allowsNull]) { +// return [NSNull null]; + return nil; + } + else { + Class theAttrClass = [theAttribute valueClass]; + id theRet; + + if ([theAttrClass isSubclassOfClass:[NSNumber class]]) { + return [NSNumber numberWithInt:0]; + } + theRet = [[[theAttrClass alloc] init] autorelease]; + if (nil == theRet) { + NSLog(@"in MCPObject defaultValueForKey:%@ , for object of class %@, (attribute of class %@) the return value will be nil!!!", iKey, [self className], theAttrClass); + } + return theRet; + } +} + + +- (NSDictionary *) primaryKey +/*" Returns a dictionary with the values of the primary key. "*/ +{ + NSMutableDictionary *theRet = [NSMutableDictionary dictionary]; + NSArray *theIdAttr = [classDescription primaryKeyAttributes]; + unsigned i; + + for (i=0; [theIdAttr count] != i; ++i) { + NSString *theKey = [(MCPAttribute *)[theIdAttr objectAtIndex:i] name]; + + if ([self valueForKey:theKey]) { + [theRet setObject:[self valueForKey:theKey] forKey:theKey]; + } + else { + [theRet setObject:[NSNull null] forKey:theKey]; + } + } + return [NSDictionary dictionaryWithDictionary:theRet]; +} + +#pragma mark Testing equality (VERY important for relation management) +- (BOOL) isEqual:(id) iObject +{ + NSArray *theIdAttr; + unsigned int i; + BOOL theRet; + + if (self == iObject) { + return YES; + } + if ([self class] != [iObject class]) { + return NO; + } + theIdAttr = [classDescription identityAttributes]; + for (i = 0; [theIdAttr count] != i; ++i) { + MCPAttribute *theAttr = [theIdAttr objectAtIndex:i]; + + theRet = theRet && [[self valueForKey:[theAttr name]] isEqual:[iObject valueForKey:[theAttr name]]]; + } + return theRet; +} + + +#pragma mark Output +- (NSString *) description +{ + return [self descriptionWithLocale:nil]; +} + +- (NSString *) descriptionWithLocale:(NSDictionary *) locale +{ + NSMutableString *theOutput = [NSMutableString string]; + unsigned int i; + NSArray *theAttributes = [classDescription attributes]; + BOOL trunc = [MCPConnection truncateLongField]; + + [theOutput appendFormat:@"MCPObject subclass : %@\n", [self className]]; + for (i=0; [theAttributes count] != i; ++i) { + MCPAttribute *theAttribute = (MCPAttribute *) [theAttributes objectAtIndex:i]; + id theValue = [self valueForKey:[theAttribute name]]; + + if (trunc) { + if (([theValue isKindOfClass:[NSString class]]) && (kLengthOfTruncationForLog < [(NSString *)theValue length])) { + theValue = [theValue substringToIndex:kLengthOfTruncationForLog]; + } + else if (([theValue isKindOfClass:[NSData class]]) && (kLengthOfTruncationForLog < [(NSData *)theValue length])) { + theValue = [NSData dataWithBytes:[theValue bytes] length:kLengthOfTruncationForLog]; + } + } + [theOutput appendFormat:@"\tAttribute %u : name = %@, value = %@\n", i, [theAttribute name], theValue]; + } + [theOutput appendString:@"\n"]; + return theOutput; +} + +#pragma mark Ordering the array for relations +- (NSString *) orderSQLForClassDescription:(MCPClassDescription *) iClassDescription +{ + NSMutableArray *theAttributes = [[NSMutableArray alloc] initWithArray:[iClassDescription identityAttributes]]; + NSMutableString *theReturn = [NSMutableString string]; + unsigned int i; + + for (i = 0; [[iClassDescription primaryKeyAttributes] count] != i; ++i) { + [theAttributes insertObject:[[iClassDescription primaryKeyAttributes] objectAtIndex:i] atIndex:[theAttributes count]]; + } + for (i = 0; [theAttributes count] != i; ++i) { + if (i) { + [theReturn appendString:@", "]; + } + else { + [theReturn appendString:@" ORDER BY "]; + } + [theReturn appendString:[(MCPAttribute *)[theAttributes objectAtIndex:i] externalName]]; + } + return theReturn; +} + +#pragma mark Anti-crash method... +- (void) setNilValueForKey:(NSString *) iKey +{ + NSLog(@"Try to set %@ to nil .... not possible, will set it to zero instead...", iKey); + [self setValue:[NSNumber numberWithInt:0] forKey:iKey]; +} + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation+Private.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation+Private.h new file mode 100644 index 00000000..c8683aab --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation+Private.h @@ -0,0 +1,46 @@ +// +// $Id: MCPRelation+Private.h 482 2009-04-05 01:38:48Z stuart02 $ +// +// MCPRelation+Private.h +// 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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import "MCPRelation.h" + +@interface MCPRelation (Private) + +#pragma mark Making some work +- (void) invalidateJoins; // Check that the joins are realistics. + +#pragma mark Setters +- (void) setOrigin:(MCPClassDescription *) iOrigin; +//- (void) setJoins:(NSArray *) iJoins; + +#pragma mark Getters +- (MCPModel *) model; + +#pragma mark Fro the controller layer and the UI +- (void) addNewDefaultJoin; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.h b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.h new file mode 100644 index 00000000..43ff027a --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.h @@ -0,0 +1,112 @@ +// +// $Id: MCPRelation.h 545 2009-04-10 14:49:45Z stuart02 $ +// +// MCPRelation.h +// 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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#import <Foundation/Foundation.h> + +@class MCPModel; +@class MCPClassDescription; +@class MCPAttribute; +@class MCPJoin; + +typedef enum { + OnDeleteNullify = 1, + OnDeleteDeny = 2, + OnDeleteCascade = 3, + OnDeleteDefault = 4, + OnDeleteNoAction = 5 +} MCPRelationDeleteRule; + +@interface MCPRelation : NSObject <NSCoding> +{ +@protected + NSString *name; // Name of the relation + MCPRelationDeleteRule deleteRule; // Delete rule : what to do of the destination when origin is deleted + MCPRelation *inverseRelation; // The inverse relation (or nil if no inverse present) + MCPClassDescription *origin; // The class description from which the relation originate + MCPClassDescription *destination; // The class description to which the relation arrives + NSMutableArray *joins; // Joining attributes (array of MCPJoin) + BOOL isToMany; // Is the relation to many (or to one) + BOOL isMandatory; // Is the relation mandatory for the class description (origin) + BOOL ownsDestination; // The origin class description owns the destination class description(ies) +} + +#pragma mark Class methods ++ (void) initialize; + ++ (NSArray *) existingDeleteRules; +- (NSArray *) existingDeleteRules; + +#pragma mark Life cycle +- (id) initWithName:(NSString *) iName from:(MCPClassDescription *) iFrom to:(MCPClassDescription *) iTo; +- (void) invalidateRelation; +- (void) dealloc; + +#pragma mark NSCoding protocol +- (id) initWithCoder:(NSCoder *) decoder; +- (void) encodeWithCoder:(NSCoder *) encoder; + +#pragma mark Managing joins +//- (MCPJoin *) addNewJoin; +- (MCPJoin *) addJoinFrom:(MCPAttribute *) iFrom to:(MCPAttribute *) iTo; +- (void) removeJoinFrom:(MCPAttribute *) iFrom to:(MCPAttribute *) iTo; +//- (void) unjoinAttribute:(MCPAttribute *) iAttribute; + +#pragma mark Setters +- (void) setDestination:(MCPClassDescription *) iDestination; +- (void) setName:(NSString *) iName; +- (void) setDeleteRule:(MCPRelationDeleteRule) iDeleteRule; +- (void) setInverseRelation:(MCPRelation *) iInverseRelation; +- (void) insertObject:(MCPJoin *) iJoin inJoinsAtIndex:(unsigned int) index; +- (void) removeObjectFromJoinsAtIndex:(unsigned int) index; +- (void) setIsToMany:(BOOL) iIsToMany; +- (void) setIsMandatory:(BOOL) iIsMandatory; +- (void) setOwnsDestintation:(BOOL) iOwnsDestination; + +#pragma mark Getters +- (NSString *) name; +- (MCPRelationDeleteRule) deleteRule; +- (MCPRelation *) inverseRelation; +- (MCPClassDescription *) origin; +- (MCPClassDescription *) destination; +- (NSArray *) joins; +- (unsigned int) countOfJoins; +- (MCPJoin *) objectInJoinsAtIndex:(unsigned int) index; +- (unsigned int) indexOfJoinIdenticalTo:(id) iJoin; +- (BOOL) isToMany; +- (BOOL) isMandatory; +- (BOOL) ownsDestination; + +#pragma mark Some Usefull methods +- (MCPAttribute *) destinationAttributeForOrigin:(MCPAttribute *) iFrom; +- (MCPAttribute *) originAttributeForDestination:(MCPAttribute *) iTo; + +#pragma mark Some general methods: +- (BOOL) isEqual:(id) iObject; +- (NSString *) descriptionWithLocale:(NSDictionary *) locale; + +@end diff --git a/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.m b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.m new file mode 100644 index 00000000..57186fdc --- /dev/null +++ b/Frameworks/MCPKit/MCPEntrepriseKit/MCPRelation.m @@ -0,0 +1,486 @@ +// +// $Id: MCPRelation.m 927 2009-06-24 10:53:07Z stuart02 $ +// +// 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 <http://mysql-cocoa.sourceforge.net/> +// More info at <http://code.google.com/p/sequel-pro/> + +#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 numberWithUnsignedInt:OnDeleteNullify], @"tag", @"Nullify", @"name", nil]]; + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:OnDeleteDeny], @"tag", @"Deny", @"name", nil]]; + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:OnDeleteCascade], @"tag", @"Cascade", @"name", nil]]; + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt:OnDeleteDefault], @"tag", @"Default", @"name", nil]]; + [theExistingDeleteRules addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInt: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]; + unsigned int 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:(unsigned int) index +{ + [joins insertObject:iJoin atIndex:index]; +} + +- (void) removeObjectFromJoinsAtIndex:(unsigned int) 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]; +} + +- (unsigned int) countOfJoins +{ + return [joins count]; +} + +- (MCPJoin *) objectInJoinsAtIndex:(unsigned int) index +{ + return (MCPJoin *)((NSNotFound != index) ? [joins objectAtIndex:index] : nil); +} + +- (unsigned int) 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 +{ + unsigned int 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 +{ + unsigned int 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]]; + unsigned int 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 |