/* EOAttributeOrdering.m Copyright (C) 1996 Free Software Foundation, Inc. Author: Ovidiu Predescu Date: 1996 This file is part of the GNUstep Database Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #import "common.h" #import "EOAttribute.h" #import "EOModel.h" #import "EOEntity.h" #import "EORelationship.h" #import "EOExpressionArray.h" #import "EOCustomValues.h" #import #import "EOFExceptions.h" @interface NSString(BeautifyAttributeName) - (NSString *)_beautifyAttributeName; @end @implementation EOAttribute static NSString *defaultCalendarFormat = @"%b %d %Y %H:%M"; static EONull *null = nil; + (void)initialize { if (null == nil) null = [[EONull null] retain]; } - (id)initWithName:(NSString*)_name { if ((self = [super init])) { ASSIGN(self->name,_name); self->entity = nil; } return self; } - (id)init { return [self initWithName:nil]; } - (void)dealloc { RELEASE(self->name); RELEASE(self->calendarFormat); RELEASE(self->clientTimeZone); RELEASE(self->serverTimeZone); RELEASE(self->columnName); RELEASE(self->definition); RELEASE(self->externalType); RELEASE(self->valueClassName); RELEASE(self->valueType); RELEASE(self->insertFormat); RELEASE(self->selectFormat); RELEASE(self->updateFormat); RELEASE(self->userDictionary); self->entity = nil; /* non-retained */ RELEASE(self->definitionArray); RELEASE(self->realAttribute); [super dealloc]; } // These methods should be here to let the library work with NeXT foundation - (id)copy { return RETAIN(self); } - (id)copyWithZone:(NSZone *)_zone { return RETAIN(self); } // Is equal only if same name; used to make aliasing ordering stable - (unsigned)hash { return [self->name hash]; } - (BOOL)setName:(NSString*)_name { if([name isEqual:_name]) return YES; if([entity attributeNamed:_name]) return NO; ASSIGN(name, _name); return YES; } + (BOOL)isValidName:(NSString*)_name { return [EOEntity isValidName:_name]; } - (BOOL)setReadOnly:(BOOL)flag { if(!flag && ([self isDerived] || [self isFlattened])) return NO; flags.isReadOnly = flag; return YES; } - (BOOL)referencesProperty:(id)property { return (flags.isDerived) ? [self->definitionArray indexOfObject:property] != NSNotFound : NO; } - (void)setDefinition:(NSString *)def { NSArray *defArray; int i, count; EOEntity *currentEntity; id realAttributeName; if (def == nil) { [NSException raise:NSInvalidArgumentException format:@"invalid (nil) definition argument!"]; } self->flags.isDerived = YES; self->flags.isFlattened = NO; ASSIGN(self->definition, def); if ([definition isNameOfARelationshipPath]) { self->flags.isFlattened = YES; defArray = [definition componentsSeparatedByString:@"."]; count = [defArray count]; RELEASE(self->definitionArray); self->definitionArray = [[NSMutableArray alloc] initWithCapacity:count]; NS_DURING { currentEntity = self->entity; for (i = 0; i < count - 1; i++) { id relationshipName, relationship; relationshipName = [defArray objectAtIndex:i]; if(![EOEntity isValidName:relationshipName]) { [[[InvalidNameException alloc] initWithName:relationshipName] raise]; } relationship = [currentEntity relationshipNamed:relationshipName]; if(relationship == nil) { [[[InvalidPropertyException alloc] initWithName:relationshipName entity:currentEntity] raise]; } if([relationship isToMany]) { [[[RelationshipMustBeToOneException alloc] initWithName:relationshipName entity:currentEntity] raise]; } [self->definitionArray addObject:relationship]; currentEntity = [relationship destinationEntity]; } realAttributeName = [defArray lastObject]; RELEASE(self->realAttribute); self->realAttribute = RETAIN([currentEntity attributeNamed:realAttributeName]); if (self->realAttribute == nil) { [[[InvalidPropertyException alloc] initWithName:realAttributeName entity:currentEntity] raise]; } [self->definitionArray addObject:self->realAttribute]; } NS_HANDLER { RELEASE(self->definitionArray); self->definitionArray = nil; [localException raise]; } NS_ENDHANDLER; } else { [self->definitionArray release]; self->definitionArray = nil; self->definitionArray = [[EOExpressionArray parseExpression:definition entity:entity replacePropertyReferences:YES] retain]; } } - (NSString *)expressionValueForContext:(id)context { return (context) ? [context expressionValueForAttribute:self] : columnName; } - (void)setEntity:(EOEntity*)_entity { self->entity = _entity; /* non-retained */ } - (EOEntity *)entity { return self->entity; } - (void)resetEntity { self->entity = nil; } - (BOOL)hasEntity { return (self->entity != nil) ? YES : NO; } - (void)setCalendarFormat:(NSString*)format { ASSIGN(self->calendarFormat, format); } - (NSString *)calendarFormat { return self->calendarFormat; } - (void)setClientTimeZone:(NSTimeZone*)tz { ASSIGN(self->clientTimeZone, tz); } - (NSTimeZone *)clientTimeZone { return self->clientTimeZone; } - (void)setServerTimeZone:(NSTimeZone*)tz { ASSIGN(self->serverTimeZone, tz); } - (NSTimeZone *)serverTimeZone { return self->serverTimeZone; } - (void)setColumnName:(NSString*)_name { ASSIGN(self->columnName, _name); } - (NSString *)columnName { return self->columnName; } - (void)setExternalType:(NSString*)type { ASSIGN(self->externalType, type); } - (NSString *)externalType { return ((self->externalType == nil) && self->flags.isFlattened) ? [self->realAttribute externalType] : self->externalType; } - (void)setValueClassName:(NSString *)_name { ASSIGN(self->valueClassName, _name); } - (NSString *)valueClassName { return ((self->valueClassName == nil) && self->flags.isFlattened) ? [self->realAttribute valueClassName] : self->valueClassName; } - (void)setValueType:(NSString *)type { ASSIGN(self->valueType, type); } - (NSString *)valueType { return ((self->valueType == nil) && self->flags.isFlattened) ? [self->realAttribute valueType] : self->valueType; } - (void)setInsertFormat:(NSString *)string { ASSIGN(self->insertFormat, string); } - (NSString *)insertFormat { return self->insertFormat; } - (void)setSelectFormat:(NSString *)string { ASSIGN(self->selectFormat, string); } - (NSString *)selectFormat { return self->selectFormat; } - (void)setUpdateFormat:(NSString*)string { ASSIGN(self->updateFormat, string); } - (NSString *)updateFormat { return self->updateFormat; } - (void)setUserDictionary:(NSDictionary *)dict { ASSIGN(self->userDictionary, dict); } - (NSDictionary *)userDictionary { return self->userDictionary; } + (NSString *)defaultCalendarFormat { return defaultCalendarFormat; } - (NSString *)name { return self->name; } - (NSString *)definition { return self->definition; } - (NSMutableArray *)definitionArray { return self->definitionArray; } - (BOOL)isDerived { return self->flags.isDerived; } - (BOOL)isFlattened { return self->flags.isFlattened; } - (BOOL)isReadOnly { return self->flags.isDerived ? YES : self->flags.isReadOnly; } /* description */ - (NSString *)description { return [[self propertyList] description]; } @end /* EOAttribute */ @implementation EOAttribute (EOAttributePrivate) + (EOAttribute*)attributeFromPropertyList:(id)propertyList { EOAttribute *attribute = nil; NSString *timeZoneName; id tmp; attribute = [[EOAttribute alloc] init]; AUTORELEASE(attribute); [attribute setName:[propertyList objectForKey:@"name"]]; [attribute setCalendarFormat:[propertyList objectForKey:@"calendarFormat"]]; timeZoneName = [propertyList objectForKey:@"clientTimeZone"]; if (timeZoneName) [attribute setClientTimeZone:[NSTimeZone timeZoneWithName:timeZoneName]]; timeZoneName = [propertyList objectForKey:@"serverTimeZone"]; if (timeZoneName) [attribute setServerTimeZone:[NSTimeZone timeZoneWithName:timeZoneName]]; [attribute setColumnName: [propertyList objectForKey:@"columnName"]]; [attribute setExternalType: [propertyList objectForKey:@"externalType"]]; [attribute setValueClassName:[propertyList objectForKey:@"valueClassName"]]; [attribute setValueType: [propertyList objectForKey:@"valueType"]]; [attribute setInsertFormat: [propertyList objectForKey:@"insertFormat"]]; [attribute setSelectFormat: [propertyList objectForKey:@"selectFormat"]]; [attribute setUpdateFormat: [propertyList objectForKey:@"updateFormat"]]; [attribute setUserDictionary:[propertyList objectForKey:@"userDictionary"]]; [attribute setReadOnly: [[propertyList objectForKey:@"isReadOnly"] isEqual:@"Y"]]; if ((tmp = [propertyList objectForKey:@"allowsNull"])) [attribute setAllowsNull:[tmp isEqual:@"Y"]]; else [attribute setAllowsNull:YES]; [attribute setWidth: [[propertyList objectForKey:@"width"] unsignedIntValue]]; /* Don't call setDefinition: now. The attributes array in entity is not yet set. */ attribute->definition = RETAIN([propertyList objectForKey:@"definition"]); return attribute; } /* WARNING: You should call this method from entity after the relationships were constructed and after the `attributes' array contains the real attributes. */ - (void)replaceStringsWithObjects { if(self->definition) { NS_DURING [self setDefinition:self->definition]; NS_HANDLER { //CATCH(PropertyDefinitionException) NSLog([localException reason]); [[self->entity model] errorInReading]; } NS_ENDHANDLER; } } - (id)propertyList { NSMutableDictionary *propertyList; propertyList = [NSMutableDictionary dictionaryWithCapacity:16]; [self encodeIntoPropertyList:propertyList]; return propertyList; } - (int)compareByName:(EOAttribute *)_other { return [[(EOAttribute *)self name] compare:[_other name]]; } @end /* EOAttribute (EOAttributePrivate) */ @implementation EOAttribute(ValuesConversion) - (id)convertValue:(id)aValue toClass:(Class)aClass forType:(NSString*)_type { // Check nil/EONull if (aValue == nil) return nil; if (aValue == [EONull null]) return aValue; // Check if we need conversion; we use is kind of because // a string is not a NSString but some concrete class, so is NSData, // NSNumber and may be other classes if ([aValue isKindOfClass:aClass]) return aValue; // We have to convert the aValue // Try EOCustomValues if ([aValue respondsToSelector:@selector(stringForType:)]) { // Special case if aClass is NSNumber if (aClass == [NSNumber class]) { return [NSNumber numberWithString:[aValue stringForType:_type] type:_type]; } // Even more Special case if aClass is NSCalendar date if (aClass == [NSCalendarDate class]) { id format, date; format = [self calendarFormat]; if (format == nil) format = [EOAttribute defaultCalendarFormat]; date = [NSCalendarDate dateWithString:[aValue stringForType:_type] calendarFormat:format]; [date setCalendarFormat:format]; return date; } // See if we can alloc a new aValue and initilize it if ([aClass instancesRespondToSelector: @selector(initWithString:type:)]) { return AUTORELEASE([[aClass alloc] initWithString:[aValue stringForType:_type] type:_type]); } } // Try EODatabaseCustomValues if ([aValue respondsToSelector:@selector(dataForType:)]) { // See if we can alloc a new aValue and initilize it if ([aClass instancesRespondToSelector: @selector(initWithData:type:)]) { return AUTORELEASE([[aClass alloc] initWithData:[aValue dataForType:_type] type:_type]); } } // Could not convert if got here return nil; } - (id)convertValueToModel:(id)aValue { id aValueClassName; Class aValueClass; // Check value class from attribute aValueClassName = [self valueClassName]; aValueClass = NSClassFromString(aValueClassName); if (aValueClass == Nil) return aValue; return [self convertValue:aValue toClass:aValueClass forType:[self valueType]]; } @end @implementation NSString (EOAttributeTypeCheck) - (BOOL)isNameOfARelationshipPath { BOOL result = NO; char buf[[self cStringLength] + 1]; const char *s; s = buf; [self getCString:buf]; if(!isalnum((int)*s) && *s != '@' && *s != '_' && *s != '#') return NO; for(++s; *s; s++) { if(!isalnum((int)*s) && *s != '@' && *s != '_' && *s != '#' && *s != '$' && *s != '.') return NO; if(*s == '.') result = YES; } return result; } @end @implementation EOAttribute(PropertyListCoding) static inline void _addToPropList(NSMutableDictionary *propertyList, id _value, NSString *key) { if (_value) [propertyList setObject:_value forKey:key]; } - (void)encodeIntoPropertyList:(NSMutableDictionary *)_plist { _addToPropList(_plist, self->name, @"name"); _addToPropList(_plist, self->calendarFormat, @"calendarFormat"); _addToPropList(_plist, self->columnName, @"columnName"); _addToPropList(_plist, self->definition, @"definition"); _addToPropList(_plist, self->externalType, @"externalType"); _addToPropList(_plist, self->valueClassName, @"valueClassName"); _addToPropList(_plist, self->valueType, @"valueType"); _addToPropList(_plist, self->insertFormat, @"insertFormat"); _addToPropList(_plist, self->selectFormat, @"selectFormat"); _addToPropList(_plist, self->updateFormat, @"updateFormat"); _addToPropList(_plist, self->userDictionary, @"userDictionary"); if (self->clientTimeZone) { [_plist setObject:[self->clientTimeZone timeZoneName] forKey:@"clientTimeZone"]; } if (self->serverTimeZone) { [_plist setObject:[self->serverTimeZone timeZoneName] forKey:@"serverTimeZone"]; } if (self->width != 0) { [_plist setObject:[NSNumber numberWithUnsignedInt:self->width] forKey:@"width"]; } if (self->flags.isReadOnly) { [_plist setObject:[NSString stringWithCString:"Y"] forKey:@"isReadOnly"]; } if (self->flags.allowsNull) { [_plist setObject:[NSString stringWithCString:"Y"] forKey:@"allowsNull"]; } } @end /* EOAttribute(PropertyListCoding) */ @implementation EOAttribute(EOF2Additions) - (void)beautifyName { [self setName:[[self name] _beautifyAttributeName]]; } /* constraints */ - (void)setAllowsNull:(BOOL)_flag { self->flags.allowsNull = _flag ? 1 : 0; } - (BOOL)allowsNull { return self->flags.allowsNull ? YES : NO; } - (void)setWidth:(unsigned)_width { self->width = _width; } - (unsigned)width { return self->width; } - (NSException *)validateValue:(id *)_value { if (_value == NULL) return nil; /* check NULL constraint */ if (!self->flags.allowsNull) { if ((*_value == nil) || (*_value == null)) { NSException *e; NSDictionary *ui; ui = [NSDictionary dictionaryWithObjectsAndKeys: *_value ? *_value : null, @"value", self, @"attribute", nil]; e = [NSException exceptionWithName:@"EOValidationException" reason:@"violated not-null constraint" userInfo:ui]; return e; } } /* check width constraint */ if (self->width != 0) { static Class NSDataClass = Nil; static Class NSStringClass = Nil; if (NSDataClass == nil) NSDataClass = [NSData class]; if (NSStringClass == nil) NSStringClass = [NSString class]; if ([[*_value class] isKindOfClass:NSDataClass]) { unsigned len; len = [*_value length]; if (len > self->width) { NSException *e; NSDictionary *ui; ui = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt:self->width], @"maxWidth", [NSNumber numberWithUnsignedInt:len], @"width", *_value ? *_value : null, @"value", self, @"attribute", nil]; e = [NSException exceptionWithName:@"EOValidationException" reason:@"data value exceeds allowed attribute width" userInfo:ui]; return e; } } else if ([[*_value class] isKindOfClass:NSStringClass]) { unsigned len; len = [*_value cStringLength]; if (len > self->width) { NSException *e; NSDictionary *ui; ui = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithUnsignedInt:self->width], @"maxWidth", [NSNumber numberWithUnsignedInt:len], @"width", *_value ? *_value : null, @"value", self, @"attribute", nil]; e = [NSException exceptionWithName:@"EOValidationException" reason:@"string value exceeds allowed attribute width" userInfo:ui]; return e; } } } return nil; } - (NSString *)readFormat { return nil; } - (NSString *)writeFormat { return nil; } @end /* EOAttribute(EOF2Additions) */ @implementation NSString(BeautifyAttributeName) - (NSString *)_beautifyAttributeName { unsigned clen = 0; char *s = NULL; unsigned cnt, cnt2; if ([self length] == 0) return @""; clen = [self cStringLength]; #if GNU_RUNTIME s = objc_atomic_malloc(clen + 4); #else s = malloc(clen + 4); #endif [self getCString:s maxLength:clen]; for (cnt = cnt2 = 0; cnt < clen; cnt++, cnt2++) { if ((s[cnt] == '_') && (s[cnt + 1] != '\0')) { s[cnt2] = toupper(s[cnt + 1]); cnt++; } else if ((s[cnt] == '2') && (s[cnt + 1] != '\0')) { s[cnt2] = s[cnt]; cnt++; cnt2++; s[cnt2] = toupper(s[cnt]); } else s[cnt2] = tolower(s[cnt]); } s[cnt2] = '\0'; #if !LIB_FOUNDATION_LIBRARY { NSString *os; os = [NSString stringWithCString:s]; free(s); return os; } #else return [NSString stringWithCStringNoCopy:s freeWhenDone:YES]; #endif } @end /* NSString(BeautifyAttributeName) */