![]() |
API 0.9.5
|
00001 /* 00002 * CPSet+KVO.j 00003 * Foundation 00004 * 00005 * Created by Daniel Stolzenberg. 00006 * Copyright 2010, University of Rostock 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 00024 @implementation CPObject (CPSetKVO) 00025 00026 - (id)mutableSetValueForKey:(id)aKey 00027 { 00028 return [[_CPKVCSet alloc] initWithKey:aKey forProxyObject:self]; 00029 } 00030 00031 - (id)mutableSetValueForKeyPath:(id)aKeyPath 00032 { 00033 var dotIndex = aKeyPath.indexOf("."); 00034 00035 if (dotIndex < 0) 00036 return [self mutableSetValueForKey:aKeyPath]; 00037 00038 var firstPart = aKeyPath.substring(0, dotIndex), 00039 lastPart = aKeyPath.substring(dotIndex + 1); 00040 00041 return [[self valueForKeyPath:firstPart] mutableSetValueForKeyPath:lastPart]; 00042 } 00043 00044 @end 00045 00046 @implementation _CPKVCSet : CPMutableSet 00047 { 00048 id _proxyObject; 00049 id _key; 00050 00051 00052 SEL _accessSEL; 00053 Function _access; 00054 00055 SEL _setSEL; 00056 Function _set; 00057 00058 SEL _countSEL; 00059 Function _count; 00060 00061 SEL _enumeratorSEL; 00062 Function _enumerator; 00063 00064 SEL _memberSEL; 00065 Function _member; 00066 00067 SEL _addSEL; 00068 Function _add; 00069 00070 SEL _addManySEL; 00071 Function _addMany; 00072 00073 SEL _removeSEL; 00074 Function _remove; 00075 00076 SEL _removeManySEL; 00077 Function _removeMany; 00078 00079 SEL _intersectSEL; 00080 Function _intersect; 00081 } 00082 00083 + (id)alloc 00084 { 00085 var set = [CPMutableSet set]; 00086 00087 set.isa = self; 00088 00089 var ivars = class_copyIvarList(self), 00090 count = ivars.length; 00091 00092 while (count--) 00093 set[ivar_getName(ivars[count])] = nil; 00094 00095 return set; 00096 } 00097 00098 - (id)initWithKey:(id)aKey forProxyObject:(id)anObject 00099 { 00100 self = [super init]; 00101 00102 _key = aKey; 00103 _proxyObject = anObject; 00104 00105 var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1); 00106 00107 _accessSEL = sel_getName(_key); 00108 if ([_proxyObject respondsToSelector:_accessSEL]) 00109 _access = [_proxyObject methodForSelector:_accessSEL]; 00110 00111 _setSEL = sel_getName(@"set"+capitalizedKey+":"); 00112 if ([_proxyObject respondsToSelector:_setSEL]) 00113 _set = [_proxyObject methodForSelector:_setSEL]; 00114 00115 _countSEL = sel_getName(@"countOf"+capitalizedKey); 00116 if ([_proxyObject respondsToSelector:_countSEL]) 00117 _count = [_proxyObject methodForSelector:_countSEL]; 00118 00119 _enumeratorSEL = sel_getName(@"enumeratorOf"+capitalizedKey); 00120 if ([_proxyObject respondsToSelector:_enumeratorSEL]) 00121 _enumerator = [_proxyObject methodForSelector:_enumeratorSEL]; 00122 00123 _memberSEL = sel_getName(@"memberOf"+capitalizedKey+":"); 00124 if ([_proxyObject respondsToSelector:_memberSEL]) 00125 _member = [_proxyObject methodForSelector:_memberSEL]; 00126 00127 _addSEL = sel_getName(@"add"+capitalizedKey+"Object:"); 00128 if ([_proxyObject respondsToSelector:_addSEL]) 00129 _add = [_proxyObject methodForSelector:_addSEL]; 00130 00131 _addManySEL = sel_getName(@"add"+capitalizedKey+":"); 00132 if ([_proxyObject respondsToSelector:_addManySEL]) 00133 _addMany = [_proxyObject methodForSelector:_addManySEL]; 00134 00135 _removeSEL = sel_getName(@"remove"+capitalizedKey+"Object:"); 00136 if ([_proxyObject respondsToSelector:_removeSEL]) 00137 _remove = [_proxyObject methodForSelector:_removeSEL]; 00138 00139 _removeManySEL = sel_getName(@"remove"+capitalizedKey+":"); 00140 if ([_proxyObject respondsToSelector:_removeManySEL]) 00141 _removeMany = [_proxyObject methodForSelector:_removeManySEL]; 00142 00143 _intersectSEL = sel_getName(@"intersect"+capitalizedKey+":"); 00144 if ([_proxyObject respondsToSelector:_intersectSEL]) 00145 _intersect = [_proxyObject methodForSelector:_intersectSEL]; 00146 00147 return self; 00148 } 00149 00150 - (id)_representedObject 00151 { 00152 if (_access) 00153 return _access(_proxyObject, _accessSEL); 00154 00155 return [_proxyObject valueForKey:_key]; 00156 } 00157 00158 - (void)_setRepresentedObject:(id)anObject 00159 { 00160 if (_set) 00161 return _set(_proxyObject, _setSEL, anObject); 00162 00163 [_proxyObject setValue:anObject forKey:_key]; 00164 } 00165 00166 - (unsigned)count 00167 { 00168 if (_count) 00169 return _count(_proxyObject, _countSEL); 00170 00171 return [[self _representedObject] count]; 00172 } 00173 00174 - (CPEnumerator)objectEnumerator 00175 { 00176 if (_enumerator) 00177 return _enumerator(_proxyObject, _enumeratorSEL); 00178 00179 return [[self _representedObject] objectEnumerator]; 00180 } 00181 00182 - (id)member:(id)anObject 00183 { 00184 if (_member) 00185 return _member(_proxyObject, _memberSEL, anObject); 00186 00187 return [[self _representedObject] member:anObject]; 00188 } 00189 00190 - (void)addObject:(id)anObject 00191 { 00192 if (_add) 00193 _add(_proxyObject, _addSEL, anObject); 00194 else if (_addMany) 00195 { 00196 var objectSet = [CPSet setWithObject: anObject]; 00197 _addMany(_proxyObject, _addManySEL, objectSet); 00198 } 00199 else 00200 { 00201 var target = [[self _representedObject] copy]; 00202 [target addObject:anObject]; 00203 [self _setRepresentedObject:target]; 00204 } 00205 } 00206 00207 - (void)addObjectsFromArray:(CPArray)objects 00208 { 00209 if (_addMany) 00210 { 00211 var objectSet = [CPSet setWithArray: objects]; 00212 _addMany(_proxyObject, _addManySEL, objectSet); 00213 } 00214 else if (_add) 00215 { 00216 var object, 00217 objectEnumerator = [objects objectEnumerator]; 00218 while (object = [objectEnumerator nextObject]) 00219 _add(_proxyObject, _addSEL, object); 00220 } 00221 else 00222 { 00223 var target = [[self _representedObject] copy]; 00224 [target addObjectsFromArray:objects]; 00225 [self _setRepresentedObject:target]; 00226 } 00227 } 00228 00229 - (void)unionSet:(CPSet)aSet 00230 { 00231 if (_addMany) 00232 _addMany(_proxyObject, _addManySEL, aSet); 00233 else if (_add) 00234 { 00235 var object, 00236 objectEnumerator = [aSet objectEnumerator]; 00237 while (object = [objectEnumerator nextObject]) 00238 _add(_proxyObject, _addSEL, object); 00239 } 00240 else 00241 { 00242 var target = [[self _representedObject] copy]; 00243 [target unionSet:aSet]; 00244 [self _setRepresentedObject:target]; 00245 } 00246 } 00247 00248 - (void)removeObject:(id)anObject 00249 { 00250 if (_remove) 00251 _remove(_proxyObject, _removeSEL, anObject); 00252 else if (_removeMany) 00253 { 00254 var objectSet = [CPSet setWithObject: anObject]; 00255 _removeMany(_proxyObject, _removeManySEL, objectSet); 00256 } 00257 else 00258 { 00259 var target = [[self _representedObject] copy]; 00260 [target removeObject:anObject]; 00261 [self _setRepresentedObject:target]; 00262 } 00263 } 00264 00265 - (void)minusSet:(CPSet)aSet 00266 { 00267 if (_removeMany) 00268 _removeMany(_proxyObject, _removeManySEL, aSet); 00269 else if (_remove) 00270 { 00271 var object, 00272 objectEnumerator = [aSet objectEnumerator]; 00273 while (object = [objectEnumerator nextObject]) 00274 _remove(_proxyObject, _removeSEL, object); 00275 } 00276 else 00277 { 00278 var target = [[self _representedObject] copy]; 00279 [target minusSet:aSet]; 00280 [self _setRepresentedObject:target]; 00281 } 00282 } 00283 00284 - (void)removeObjectsInArray:(CPArray)objects 00285 { 00286 if (_removeMany) 00287 { 00288 var objectSet = [CPSet setWithArray:objects]; 00289 _removeMany(_proxyObject, _removeManySEL, objectSet); 00290 } 00291 else if (_remove) 00292 { 00293 var object, 00294 objectEnumerator = [objects objectEnumerator]; 00295 while (object = [objectEnumerator nextObject]) 00296 _remove(_proxyObject, _removeSEL, object); 00297 } 00298 else 00299 { 00300 var target = [[self _representedObject] copy]; 00301 [target removeObjectsInArray:objects]; 00302 [self _setRepresentedObject:target]; 00303 } 00304 } 00305 00306 - (void)removeAllObjects 00307 { 00308 if (_removeMany) 00309 { 00310 var allObjectsSet = [[self _representedObject] copy]; 00311 _removeMany(_proxyObject, _removeManySEL, allObjectsSet); 00312 } 00313 else if (_remove) 00314 { 00315 var object, 00316 objectEnumerator = [[[self _representedObject] copy] objectEnumerator]; 00317 while (object = [objectEnumerator nextObject]) 00318 _remove(_proxyObject, _removeSEL, object); 00319 } 00320 else 00321 { 00322 var target = [[self _representedObject] copy]; 00323 [target removeAllObjects]; 00324 [self _setRepresentedObject:target]; 00325 } 00326 } 00327 00328 - (void)intersectSet:(CPSet)aSet 00329 { 00330 if (_intersect) 00331 _intersect(_proxyObject, _intersectSEL, aSet); 00332 else 00333 { 00334 var target = [[self _representedObject] copy]; 00335 [target intersectSet:aSet]; 00336 [self _setRepresentedObject:target]; 00337 } 00338 } 00339 00340 - (void)setSet:(CPSet)set 00341 { 00342 [self _setRepresentedObject: set]; 00343 } 00344 00345 - (CPArray)allObjects 00346 { 00347 return [[self _representedObject] allObjects]; 00348 } 00349 00350 - (id)anyObject 00351 { 00352 return [[self _representedObject] anyObject]; 00353 } 00354 00355 - (BOOL)containsObject:(id)anObject 00356 { 00357 return [[self _representedObject] containsObject: anObject]; 00358 } 00359 00360 - (BOOL)intersectsSet:(CPSet)aSet 00361 { 00362 return [[self _representedObject] intersectsSet: aSet]; 00363 } 00364 00365 - (BOOL)isEqualToSet:(CPSet)aSet 00366 { 00367 return [[self _representedObject] isEqualToSet: aSet]; 00368 } 00369 00370 - (id)copy 00371 { 00372 return [[self _representedObject] copy]; 00373 } 00374 00375 @end 00376 00377 @implementation CPSet (CPKeyValueCoding) 00378 00379 - (id)valueForKeyPath:(CPString)aKeyPath 00380 { 00381 if (aKeyPath.indexOf("@") === 0) 00382 { 00383 var dotIndex = aKeyPath.indexOf("."), 00384 operator, 00385 parameter; 00386 00387 if (dotIndex !== -1) 00388 { 00389 operator = aKeyPath.substring(1, dotIndex); 00390 parameter = aKeyPath.substring(dotIndex + 1); 00391 } 00392 else 00393 operator = aKeyPath.substring(1); 00394 00395 if (kvoOperators[operator]) 00396 return kvoOperators[operator](self, _cmd, parameter); 00397 00398 return nil; 00399 } 00400 else 00401 { 00402 var valuesForKeySet = [CPSet set], 00403 containedObject, 00404 containedObjectValue, 00405 containedObjectEnumerator = [self objectEnumerator]; 00406 while (containedObject = [containedObjectEnumerator nextObject]) 00407 { 00408 containedObjectValue = [containedObject valueForKeyPath:aKeyPath]; 00409 if (containedObjectValue) 00410 [valuesForKeySet addObject:containedObjectValue]; 00411 } 00412 return valuesForKeySet; 00413 } 00414 } 00415 00416 - (void)setValue:(id)aValue forKey:(CPString)aKey 00417 { 00418 var containedObject, 00419 containedObjectEnumerator = [self objectEnumerator]; 00420 while (containedObject = [containedObjectEnumerator nextObject]) 00421 [containedObject setValue:aValue forKey:aKey]; 00422 } 00423 00424 @end 00425 00426 var kvoOperators = []; 00427 00428 // FIXME: this is not DRY because the implementation in CPArray+KVO is merely the same! 00429 // HACK: prevent these from becoming globals. workaround for obj-j "function foo(){}" behavior 00430 var avgOperator, 00431 maxOperator, 00432 minOperator, 00433 countOperator, 00434 sumOperator; 00435 00436 kvoOperators["avg"] = function avgOperator(self, _cmd, param) 00437 { 00438 //CPSet returns a CPSet - to obtain an array call allObjects 00439 var objects = [[self valueForKeyPath:param] allObjects], 00440 length = [objects count], 00441 index = length, 00442 average = 0.0; 00443 00444 if (!length) 00445 return 0; 00446 00447 while (index--) 00448 average += [objects[index] doubleValue]; 00449 00450 return average / length; 00451 } 00452 00453 kvoOperators["max"] = function maxOperator(self, _cmd, param) 00454 { 00455 //CPSet returns a CPSet - to obtain an array call allObjects 00456 var objects = [[self valueForKeyPath:param] allObjects], 00457 index = [objects count] - 1, 00458 max = [objects lastObject]; 00459 00460 while (index--) 00461 { 00462 var item = objects[index]; 00463 if ([max compare:item] < 0) 00464 max = item; 00465 } 00466 00467 return max; 00468 } 00469 00470 kvoOperators["min"] = function minOperator(self, _cmd, param) 00471 { 00472 //CPSet returns a CPSet - to obtain an array call allObjects 00473 var objects = [[self valueForKeyPath:param] allObjects], 00474 index = [objects count] - 1, 00475 min = [objects lastObject]; 00476 00477 while (index--) 00478 { 00479 var item = objects[index]; 00480 if ([min compare:item] > 0) 00481 min = item; 00482 } 00483 00484 return min; 00485 } 00486 00487 kvoOperators["count"] = function countOperator(self, _cmd, param) 00488 { 00489 return [self count]; 00490 } 00491 00492 kvoOperators["sum"] = function sumOperator(self, _cmd, param) 00493 { 00494 //CPSet returns a CPSet - to obtain an array call allObjects 00495 var objects = [[self valueForKeyPath:param] allObjects], 00496 index = [objects count], 00497 sum = 0.0; 00498 00499 while (index--) 00500 sum += [objects[index] doubleValue]; 00501 00502 return sum; 00503 } 00504 00505 00506 @implementation CPSet (CPKeyValueObserving) 00507 00508 - (void)addObserver:(id)observer forKeyPath:(CPString)aKeyPath options:(unsigned)options context:(id)context 00509 { 00510 if ([isa instanceMethodForSelector:_cmd] === [CPSet instanceMethodForSelector:_cmd]) 00511 [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPSet"]; 00512 else 00513 [super addObserver:observer forKeyPath:aKeyPath options:options context:context]; 00514 } 00515 00516 - (void)removeObserver:(id)observer forKeyPath:(CPString)aKeyPath 00517 { 00518 if ([isa instanceMethodForSelector:_cmd] === [CPSet instanceMethodForSelector:_cmd]) 00519 [CPException raise:CPInvalidArgumentException reason:"Unsupported method on CPSet"]; 00520 else 00521 [super removeObserver:observer forKeyPath:aKeyPath]; 00522 } 00523 00524 @end