1"""
2This module implements a callback function that is used by the C code to
3add Python special methods to Objective-C classes with a suitable interface.
4
5This module contains no user callable code.
6
7TODO:
8- Add external interface: Framework specific modules may want to add to this.
9
10- These are candidates for implementation:
11
12    >>> from Foundation import *
13    >>> set(dir(list)) - set(dir(NSMutableArray))
14    set(['__delslice__', '__imul__', '__getslice__', '__setslice__',
15        '__iadd__', '__mul__', '__add__', '__rmul__'])
16    >>> set(dir(dict)) - set(dir(NSMutableDictionary))
17    set(['__cmp__'])
18
19"""
20from objc._objc import _setClassExtender, selector, lookUpClass, currentBundle, repythonify, splitSignature, _block_call
21from objc._objc import registerMetaDataForSelector
22from itertools import imap
23import sys
24
25__all__ = ( 'addConvenienceForSelector', 'addConvenienceForClass' )
26
27
28# 12041508: NSDictionary now defines both objectForKey: and containsObject:,
29# so that the defining of __contains__ is order dependent (and often wrong).
30# So we define an OVERRIDE dict, whose keys are pipe-separated, triple strings:
31#
32# 1) the python method in question
33# 2) the first selector
34# 3) the overriding selector
35#
36# So "__contains__|containsObject:|objectForKey:" means that if containsObject:
37# was first, and has already defined __contains__, the objectForKey: can
38# later override the definition of __contains__ (but not in the reverse order).
39OVERRIDE = {'__contains__|containsObject:|objectForKey:': 1}
40def _canOverride(meth, owner, sel):
41    if not meth in owner:
42        return False
43    k = meth + '|' + owner[meth] + '|' + sel
44    return k in OVERRIDE
45
46CONVENIENCE_METHODS = {}
47CLASS_METHODS = {}
48
49def addConvenienceForSelector(selector, methods):
50    """
51    Add the list with methods to every class that has a selector with the
52    given name.
53    """
54    CONVENIENCE_METHODS[selector] = methods
55
56def addConvenienceForClass(classname, methods):
57    """
58    Add the list with methods to the class with the specified name
59    """
60    CLASS_METHODS[classname] = methods
61
62NSObject = lookUpClass('NSObject')
63
64def add_convenience_methods(super_class, name, type_dict):
65    try:
66        return _add_convenience_methods(super_class, name, type_dict)
67    except:
68        import traceback
69        traceback.print_exc()
70        raise
71
72def _add_convenience_methods(super_class, name, type_dict):
73    """
74    Add additional methods to the type-dict of subclass 'name' of
75    'super_class'.
76
77    CONVENIENCE_METHODS is a global variable containing a mapping from
78    an Objective-C selector to a Python method name and implementation.
79
80    CLASS_METHODS is a global variable containing a mapping from
81    class name to a list of Python method names and implementation.
82
83    Matching entries from both mappings are added to the 'type_dict'.
84    """
85    if type_dict.get('__objc_python_subclass__'):
86        if 'bundleForClass' not in type_dict:
87            cb = currentBundle()
88            def bundleForClass(cls):
89                return cb
90            type_dict['bundleForClass'] = selector(bundleForClass, isClassMethod=True)
91        if '__bundle_hack__' in type_dict:
92            import warnings
93            warnings.warn(
94                "__bundle_hack__ is not necessary in PyObjC 1.3+ / py2app 0.1.8+",
95                DeprecationWarning)
96
97    look_at_super = (super_class is not None and super_class.__name__ != 'Object')
98
99    owner = {}
100    for k, sel in type_dict.items():
101        if not isinstance(sel, selector):
102            continue
103
104        #
105        # Handle some common exceptions to the usual rules:
106        #
107
108        sel = sel.selector
109
110        if sel in CONVENIENCE_METHODS:
111            v = CONVENIENCE_METHODS[sel]
112            for nm, value in v:
113                if nm in type_dict and isinstance(type_dict[nm], selector):
114
115                    # Clone attributes of already existing version
116
117                    t = type_dict[nm]
118                    v = selector(value, selector=t.selector,
119                        signature=t.signature, isClassMethod=t.isClassMethod)
120
121                    type_dict[nm] = v
122
123                elif look_at_super and hasattr(super_class, nm):
124                    # Skip, inherit the implementation from a super_class
125                    pass
126
127                elif nm not in type_dict or _canOverride(nm, owner, sel):
128                    type_dict[nm] = value
129                    owner[nm] = sel
130
131    if name in CLASS_METHODS:
132        for nm, value in CLASS_METHODS[name]:
133            type_dict[nm] = value
134
135
136    if name == 'NSObject':
137        class kvc (object):
138            """
139            Key-Value-Coding accessor for Cocoa objects.
140
141            Both attribute access and dict-like indexing will attempt to
142            access the requested item through Key-Value-Coding.
143            """
144            __slots__ = ('__object',)
145            def __init__(self, value):
146                self.__object = value
147
148            def __repr__(self):
149                return "<KVC accessor for %r>"%(self.__object,)
150
151            def __getattr__(self, key):
152                try:
153                    return self.__object.valueForKey_(key)
154                except KeyError, msg:
155                    if hasattr(msg, '_pyobjc_info_') and msg._pyobjc_info_['name'] == 'NSUnknownKeyException':
156                        raise AttributeError(key)
157
158                    raise
159            def __setattr__(self, key, value):
160                if not key.startswith('_'):
161                    return self.__object.setValue_forKey_(value, key)
162                else:
163                    super(kvc, self).__setattr__(key, value)
164
165            def __getitem__(self, key):
166                if not isinstance(key, (str, unicode)):
167                    raise TypeError("Key must be string")
168                return self.__object.valueForKey_(key)
169
170            def __setitem__(self, key, value):
171                if not isinstance(key, (str, unicode)):
172                    raise TypeError("Key must be string")
173                return self.__object.setValue_forKey_(value, key)
174
175        type_dict['_'] = property(kvc)
176
177_setClassExtender(add_convenience_methods)
178
179
180#
181# The following conveniences should strictly speaking be in
182# in pyobjc-framework-Foundation, but as they are very fundamental
183# we're keeping them here.
184#
185
186def __getitem__objectForKey_(self, key):
187    res = self.objectForKey_(container_wrap(key))
188    return container_unwrap(res, KeyError, key)
189
190def has_key_objectForKey_(self, key):
191    res = self.objectForKey_(container_wrap(key))
192    return res is not None
193
194def get_objectForKey_(self, key, dflt=None):
195    res = self.objectForKey_(container_wrap(key))
196    if res is None:
197        res = dflt
198    return res
199
200CONVENIENCE_METHODS[b'objectForKey:'] = (
201    ('__getitem__', __getitem__objectForKey_),
202    ('has_key', has_key_objectForKey_),
203    ('get', get_objectForKey_),
204    ('__contains__', has_key_objectForKey_),
205)
206
207def __delitem__removeObjectForKey_(self, key):
208    self.removeObjectForKey_(container_wrap(key))
209
210CONVENIENCE_METHODS[b'removeObjectForKey:'] = (
211    ('__delitem__', __delitem__removeObjectForKey_),
212)
213
214def update_setObject_forKey_(self, *args, **kwds):
215    # XXX - should this be more flexible?
216    if len(args) == 0:
217        pass
218    elif len(args) != 1:
219        raise TypeError("update expected at most 1 arguments, got {0}".format(
220            len(args)))
221
222
223    else:
224        other = args[0]
225        if hasattr(other, 'keys'):
226            # This mirrors the implementation of dict.update, but seems
227            # wrong for Python3 (with collectons.Dict)
228            for key in other.keys():
229                self[key] = other[key]
230
231        else:
232            for key, value in other:
233                self[key] = value
234
235    for k, v in kwds.iteritems():
236        self[k] = v
237
238def setdefault_setObject_forKey_(self, key, dflt=None):
239    try:
240        return self[key]
241    except KeyError:
242        self[key] = dflt
243        return dflt
244
245def __setitem__setObject_forKey_(self, key, value):
246    self.setObject_forKey_(container_wrap(value), container_wrap(key))
247
248pop_setObject_dflt=object()
249def pop_setObject_forKey_(self, key, dflt=pop_setObject_dflt):
250    try:
251        res = self[key]
252    except KeyError:
253        if dflt == pop_setObject_dflt:
254            raise KeyError(key)
255        res = dflt
256    else:
257        del self[key]
258    return res
259
260NSAutoreleasePool = lookUpClass('NSAutoreleasePool')
261
262def popitem_setObject_forKey_(self):
263    try:
264        it = self.keyEnumerator()
265        k = container_unwrap(it.nextObject(), StopIteration)
266    except (StopIteration, IndexError):
267        raise KeyError, "popitem on an empty %s" % (type(self).__name__,)
268    else:
269        result = (k, container_unwrap(self.objectForKey_(k), KeyError))
270        self.removeObjectForKey_(k)
271        return result
272
273CONVENIENCE_METHODS[b'setObject:forKey:'] = (
274    ('__setitem__', __setitem__setObject_forKey_),
275    ('update', update_setObject_forKey_),
276    ('setdefault', setdefault_setObject_forKey_),
277    ('pop', pop_setObject_forKey_),
278    ('popitem', popitem_setObject_forKey_),
279)
280
281
282CONVENIENCE_METHODS[b'count'] = (
283    ('__len__', lambda self: self.count()),
284)
285
286CONVENIENCE_METHODS[b'containsObject:'] = (
287    ('__contains__', lambda self, elem: bool(self.containsObject_(container_wrap(elem)))),
288)
289
290
291
292def objc_hash(self, _max=sys.maxint, _const=((sys.maxint + 1L) * 2L)):
293    rval = self.hash()
294    if rval > _max:
295        rval -= _const
296        # -1 is not a valid hash in Python and hash(x) will
297        # translate a hash of -1 to -2, so we might as well
298        # do it here so that it's not too surprising..
299        if rval == -1:
300            rval = -2
301    return int(rval)
302CONVENIENCE_METHODS[b'hash'] = (
303    ('__hash__', objc_hash),
304)
305
306CONVENIENCE_METHODS[b'isEqualTo:'] = (
307    ('__eq__', lambda self, other: bool(self.isEqualTo_(other))),
308)
309
310CONVENIENCE_METHODS[b'isEqual:'] = (
311    ('__eq__', lambda self, other: bool(self.isEqual_(other))),
312)
313
314CONVENIENCE_METHODS[b'isGreaterThan:'] = (
315    ('__gt__', lambda self, other: bool(self.isGreaterThan_(other))),
316)
317
318CONVENIENCE_METHODS[b'isGreaterThanOrEqualTo:'] = (
319    ('__ge__', lambda self, other: bool(self.isGreaterThanOrEqualTo_(other))),
320)
321
322CONVENIENCE_METHODS[b'isLessThan:'] = (
323    ('__lt__', lambda self, other: bool(self.isLessThan_(other))),
324)
325
326CONVENIENCE_METHODS[b'isLessThanOrEqualTo:'] = (
327    ('__le__', lambda self, other: bool(self.isLessThanOrEqualTo_(other))),
328)
329
330CONVENIENCE_METHODS[b'isNotEqualTo:'] = (
331    ('__ne__', lambda self, other: bool(self.isNotEqualTo_(other))),
332)
333
334CONVENIENCE_METHODS[b'length'] = (
335    ('__len__', lambda self: self.length()),
336)
337
338CONVENIENCE_METHODS[b'addObject:'] = (
339    ('append', lambda self, item: self.addObject_(container_wrap(item))),
340)
341
342def reverse_exchangeObjectAtIndex_withObjectAtIndex_(self):
343    begin = 0
344    end = len(self) - 1
345    while begin < end:
346        self.exchangeObjectAtIndex_withObjectAtIndex_(begin, end)
347        begin += 1
348        end -= 1
349
350CONVENIENCE_METHODS[b'exchangeObjectAtIndex:withObjectAtIndex:'] = (
351    ('reverse', reverse_exchangeObjectAtIndex_withObjectAtIndex_),
352)
353
354def ensureArray(anArray):
355    if not isinstance(anArray, (NSArray, list, tuple)):
356        anArray = list(anArray)
357    return anArray
358
359
360def extend_addObjectsFromArray_(self, anArray):
361    self.addObjectsFromArray_(ensureArray(anArray))
362
363CONVENIENCE_METHODS[b'addObjectsFromArray:'] = (
364    ('extend', extend_addObjectsFromArray_),
365)
366
367_index_sentinel=object()
368def index_indexOfObject_inRange_(self, item, start=0, stop=_index_sentinel):
369    #from Foundation import NSNotFound
370    NSNotFound = sys.maxsize
371    if start == 0 and stop is _index_sentinel:
372        res = self.indexOfObject_(container_wrap(item))
373        if res == NSNotFound:
374            raise ValueError("%s.index(x): x not in list" % (type(self).__name__,))
375    else:
376        l = len(self)
377        if start < 0:
378            start = l + start
379            if start < 0:
380                start = 0
381
382        if stop is not _index_sentinel:
383            if stop < 0:
384                stop = l + stop
385                if stop < 0:
386                    stop = 0
387        else:
388            stop = l
389
390        itemcount = len(self)
391
392        if itemcount == 0:
393            raise ValueError("%s.index(x): x not in list" % (type(self).__name__,))
394
395        else:
396            if start >= itemcount:
397                start = itemcount - 1
398            if stop >= itemcount:
399                stop = itemcount - 1
400
401            if stop <= start:
402                ln = 0
403            else:
404
405                ln = stop - start
406
407
408            if ln == 0:
409                raise ValueError("%s.index(x): x not in list" % (type(self).__name__,))
410
411            if ln > sys.maxint:
412                ln = sys.maxint
413
414            res = self.indexOfObject_inRange_(item, (start, ln))
415            if res == NSNotFound:
416                raise ValueError("%s.index(x): x not in list" % (type(self).__name__,))
417    return res
418
419CONVENIENCE_METHODS[b'indexOfObject:inRange:'] = (
420    ('index', index_indexOfObject_inRange_),
421)
422
423def insert_insertObject_atIndex_(self, idx, item):
424    if idx < 0:
425        idx += len(self)
426        if idx < 0:
427            raise IndexError("list index out of range")
428    self.insertObject_atIndex_(container_wrap(item), idx)
429
430CONVENIENCE_METHODS[b'insertObject:atIndex:'] = (
431    ( 'insert', insert_insertObject_atIndex_),
432)
433
434def __getitem__objectAtIndex_(self, idx):
435    if isinstance(idx, slice):
436        start, stop, step = idx.indices(len(self))
437        #if step == 1:
438        #    m = getattr(self, 'subarrayWithRange_', None)
439        #    if m is not None:
440        #        return m((start, stop - start))
441        return [self[i] for i in xrange(start, stop, step)]
442
443    elif not isinstance(idx, (int, long)):
444        raise TypeError("index must be a number")
445
446    if idx < 0:
447        idx += len(self)
448        if idx < 0:
449            raise IndexError("list index out of range")
450
451    return container_unwrap(self.objectAtIndex_(idx), RuntimeError)
452
453def __getslice__objectAtIndex_(self, i, j):
454    i = max(i, 0); j = max(j, 0)
455    return __getitem__objectAtIndex_(self, slice(i, j))
456
457CONVENIENCE_METHODS[b'objectAtIndex:'] = (
458    ('__getitem__', __getitem__objectAtIndex_),
459    ('__getslice__', __getslice__objectAtIndex_),
460)
461
462def __delitem__removeObjectAtIndex_(self, idx):
463    if isinstance(idx, slice):
464        start, stop, step = idx.indices(len(self))
465        if step == 1:
466            if start > stop:
467                start, stop = stop, start
468            m = getattr(self, 'removeObjectsInRange_', None)
469            if m is not None:
470                m((start, stop - start))
471                return
472        r = range(start, stop, step)
473        r.sort()
474        r.reverse()
475        for i in r:
476            self.removeObjectAtIndex_(i)
477        return
478    if idx < 0:
479        idx += len(self)
480        if idx < 0:
481            raise IndexError("list index out of range")
482
483    self.removeObjectAtIndex_(idx)
484
485def __delslice__removeObjectAtIndex_(self, i, j):
486    __delitem__removeObjectAtIndex_(self, slice(i, j))
487
488def pop_removeObjectAtIndex_(self, idx=-1):
489    length = len(self)
490    if length <= 0:
491        raise IndexError("pop from empty list")
492    elif idx >= length or (idx + length) < 0:
493        raise IndexError("pop index out of range")
494    elif idx < 0:
495        idx += len(self)
496        if idx < 0:
497            raise IndexError("list index out of range")
498    rval = self[idx]
499    self.removeObjectAtIndex_(idx)
500    return rval
501
502def remove_removeObjectAtIndex_(self, obj):
503    idx = self.index(obj)
504    self.removeObjectAtIndex_(idx)
505
506CONVENIENCE_METHODS[b'removeObjectAtIndex:'] = (
507    ('remove', remove_removeObjectAtIndex_),
508    ('pop', pop_removeObjectAtIndex_),
509    ('__delitem__', __delitem__removeObjectAtIndex_),
510    ('__delslice__', __delslice__removeObjectAtIndex_),
511)
512
513def __setitem__replaceObjectAtIndex_withObject_(self, idx, anObject):
514    if isinstance(idx, slice):
515        start, stop, step = idx.indices(len(self))
516        if step >=0:
517            if stop <= start:
518                # Empty slice: insert values
519                stop = start
520        elif start <= stop:
521            start = stop
522
523        if step == 1:
524            m = getattr(self, 'replaceObjectsInRange_withObjectsFromArray_', None)
525            if m is not None:
526                m((start, stop - start), ensureArray(anObject))
527                return
528
529        if not isinstance(anObject, (NSArray, list, tuple)):
530            anObject = list(anObject)
531
532        slice_len = len(xrange(start, stop, step))
533        if slice_len != len(anObject):
534            raise ValueError("Replacing extended slice with %d elements by %d elements"%(
535                slice_len, len(anObject)))
536
537        if step > 0:
538            if anObject is self:
539                toAssign = list(anObject)
540            else:
541                toAssign = anObject
542            for inIdx, outIdx in enumerate(xrange(start, stop, step)):
543                self.replaceObjectAtIndex_withObject_(outIdx, toAssign[inIdx])
544
545        elif step == 0:
546            raise ValueError("Step 0")
547
548        else:
549            if anObject is self:
550                toAssign = list(anObject)
551            else:
552                toAssign = anObject
553            #for inIdx, outIdx in reversed(enumerate(reversed(range(start, stop, step)))):
554            for inIdx, outIdx in enumerate(xrange(start, stop, step)):
555                self.replaceObjectAtIndex_withObject_(outIdx, toAssign[inIdx])
556
557
558    elif not isinstance(idx, (int, long)):
559        raise TypeError("index is not an integer")
560
561    else:
562
563        if idx < 0:
564            idx += len(self)
565            if idx < 0:
566                raise IndexError("list index out of range")
567
568        self.replaceObjectAtIndex_withObject_(idx, anObject)
569
570def __setslice__replaceObjectAtIndex_withObject_(self, i, j, seq):
571    i = max(i, 0)
572    j = max(j, 0)
573    __setitem__replaceObjectAtIndex_withObject_(self, slice(i, j), seq)
574
575CONVENIENCE_METHODS[b'replaceObjectAtIndex:withObject:'] = (
576    ('__setitem__', __setitem__replaceObjectAtIndex_withObject_),
577    ('__setslice__', __setslice__replaceObjectAtIndex_withObject_),
578)
579
580def enumeratorGenerator(anEnumerator):
581    while True:
582        yield container_unwrap(anEnumerator.nextObject(), StopIteration)
583
584def dictItems(aDict):
585    """
586    NSDictionary.items()
587    """
588    keys = aDict.allKeys()
589    return zip(keys, imap(aDict.__getitem__, keys))
590
591#CONVENIENCE_METHODS[b'allKeys'] = (
592#    ('keys', lambda self: self.allKeys()),
593#    ('items', lambda self: dictItems(self)),
594#)
595
596#CONVENIENCE_METHODS[b'allValues'] = (
597    #('values', lambda self: self.allValues()),
598#)
599
600def itemsGenerator(aDict):
601    for key in aDict:
602        yield (key, aDict[key])
603
604def __iter__objectEnumerator_keyEnumerator(self):
605    meth = getattr(self, 'keyEnumerator', None)
606    if meth is None:
607        meth = self.objectEnumerator
608    return iter(meth())
609
610CONVENIENCE_METHODS[b'keyEnumerator'] = (
611    ('__iter__', __iter__objectEnumerator_keyEnumerator),
612    ('iterkeys', lambda self: iter(self.keyEnumerator())),
613    ('iteritems', lambda self: itemsGenerator(self)),
614)
615
616CONVENIENCE_METHODS[b'objectEnumerator'] = (
617    ('__iter__', __iter__objectEnumerator_keyEnumerator),
618    ('itervalues', lambda self: iter(self.objectEnumerator())),
619)
620
621CONVENIENCE_METHODS[b'reverseObjectEnumerator'] = (
622    ('__reversed__', lambda self: iter(self.reverseObjectEnumerator())),
623)
624
625CONVENIENCE_METHODS[b'removeAllObjects'] = (
626    ('clear', lambda self: self.removeAllObjects()),
627)
628
629CONVENIENCE_METHODS[b'dictionaryWithDictionary:'] = (
630    ('copy', lambda self: type(self).dictionaryWithDictionary_(self)),
631)
632
633CONVENIENCE_METHODS[b'nextObject'] = (
634    ('__iter__', enumeratorGenerator),
635)
636
637#
638# NSNumber seems to be and abstract base-class that is implemented using
639# NSCFNumber, a CoreFoundation 'proxy'.
640#
641NSNull = lookUpClass('NSNull')
642NSArray = lookUpClass('NSArray')
643#null = NSNull.null()
644
645number_wrap = repythonify
646
647def container_wrap(v):
648    if v is None:
649        return NSNull.null()
650    return v
651
652def container_unwrap(v, exc_type, *exc_args):
653    if v is None:
654        raise exc_type(*exc_args)
655    elif v is NSNull.null():
656        return None
657    return v
658
659
660def fromkeys_dictionaryWithObjects_forKeys_(cls, keys, values=None):
661    if not isinstance(keys, (list, tuple)):
662        keys = list(keys)
663    if values is None:
664        values = (None,) * len(keys)
665    elif not isinstance(values, (list, tuple)):
666        values = list(values)
667    return cls.dictionaryWithObjects_forKeys_(values, keys)
668
669#CONVENIENCE_METHODS[b'dictionaryWithObjects:forKeys:'] = (
670    #('fromkeys',
671        #classmethod(fromkeys_dictionaryWithObjects_forKeys_)),
672#)
673
674if sys.version_info[0] == 3:
675    def cmp(a, b):
676        if a == b:
677            return 0
678        elif a < b:
679            return -1
680        else:
681            return 1
682
683def sort(self, key=None, reverse=False, cmpfunc=cmp):
684    # NOTE: cmpfunc argument is for backward compatibility.
685    if key is None:
686        if reverse:
687            def doCmp(a, b, cmpfunc):
688                return -cmpfunc(a, b)
689        else:
690            def doCmp(a, b, cmpfunc):
691                return cmpfunc(a, b)
692    else:
693        # This is (a lot) slower than the algoritm used for
694        # list.sort, but so be it.
695        if reverse:
696            def doCmp(a, b, cmpfunc):
697                return -cmpfunc(key(a), key(b))
698        else:
699            def doCmp(a, b, cmpfunc):
700                return cmpfunc(key(a), key(b))
701
702    self.sortUsingFunction_context_(doCmp, cmpfunc)
703
704
705registerMetaDataForSelector(b"NSObject", b"sortUsingFunction:context:",
706        dict(
707            arguments={
708                2:  {
709                        'callable': {
710                            'reval': 'i',
711                            'arguments': {
712                                0: { 'type': '@' },
713                                1: { 'type': '@' },
714                                2: { 'type': '@' },
715                            }
716                        },
717                        'callable_retained': False,
718                },
719                3:  { 'type': '@' },
720            },
721        ))
722
723
724CONVENIENCE_METHODS[b'sortUsingFunction:context:'] = (
725    ('sort', sort),
726)
727
728CONVENIENCE_METHODS[b'hasPrefix:'] = (
729    ('startswith', lambda self, pfx: self.hasPrefix_(pfx)),
730)
731
732CONVENIENCE_METHODS[b'hasSuffix:'] = (
733    ('endswith', lambda self, pfx: self.hasSuffix_(pfx)),
734)
735
736
737CONVENIENCE_METHODS[b'copyWithZone:'] = (
738    ('__copy__', lambda self: self.copyWithZone_(None)),
739)
740
741# This won't work:
742#NSKeyedArchiver = lookUpClass('NSKeyedArchiver')
743#NSKeyedUnarchiver = lookUpClass('NSKeyedUnarchiver')
744#def coder_deepcopy(self, memo):
745#   buf = NSKeyedArchiver.archivedDataWithRootObject_(self)
746#   result = NSKeyedUnarchiver.unarchiveObjectWithData_(buf)
747#   return result
748#
749#CONVENIENCE_METHODS['encodeWithCoder:'] = (
750#   ('__deepcopy__', coder_deepcopy ),
751#)
752
753CLASS_METHODS['NSNull'] = (
754    ('__nonzero__',  lambda self: False ),
755    ('__bool__',  lambda self: False ),
756)
757
758NSDecimalNumber = lookUpClass('NSDecimalNumber')
759def _makeD(v):
760    if isinstance(v, NSDecimalNumber):
761        return v
762    return NSDecimalNumber.decimalNumberWithDecimal_(v)
763
764CLASS_METHODS['NSDecimalNumber'] = (
765    ('__add__', lambda self, other: _makeD(self.decimalValue() + other)),
766    ('__radd__', lambda self, other: _makeD(other + self.decimalValue())),
767    ('__sub__', lambda self, other: _makeD(self.decimalValue() - other)),
768    ('__rsub__', lambda self, other: _makeD(other - self.decimalValue())),
769    ('__mul__', lambda self, other: _makeD(self.decimalValue() * other)),
770    ('__rmul__', lambda self, other: _makeD(other * self.decimalValue())),
771    ('__div__', lambda self, other: _makeD(self.decimalValue() / other)),
772    ('__rdiv__', lambda self, other: _makeD(other / self.decimalValue())),
773    ('__mod__', lambda self, other: _makeD(self.decimalValue() % other)),
774    ('__rmod__', lambda self, other: _makeD(other % self.decimalValue())),
775    ('__neg__', lambda self: _makeD(-(self.decimalValue()))),
776    ('__pos__', lambda self: _makeD(+(self.decimalValue()))),
777    ('__abs__', lambda self: _makeD(abs(self.decimalValue()))),
778)
779
780def NSData__getslice__(self, i, j):
781    return self.bytes()[i:j]
782
783def NSData__getitem__(self, item):
784    buff = self.bytes()
785    try:
786        return buff[item]
787    except TypeError:
788        return buff[:][item]
789
790
791if sys.version_info[:2] <= (2,6):
792    def NSData__str__(self):
793        return self.bytes()[:]
794
795elif sys.version_info[0] == 2:
796    def NSData__str__(self):
797        return str(self.bytes().tobytes())
798
799else:
800    def NSData__str__(self):
801        return str(self.bytes().tobytes())
802
803
804CLASS_METHODS['NSData'] = (
805    ('__str__', NSData__str__),
806    ('__getitem__', NSData__getitem__),
807    ('__getslice__', NSData__getslice__),
808)
809
810def NSMutableData__setslice__(self, i, j, sequence):
811    # XXX - could use replaceBytes:inRange:, etc.
812    self.mutableBytes()[i:j] = sequence
813
814def NSMutableData__setitem__(self, item, value):
815    self.mutableBytes()[item] = value
816
817CLASS_METHODS['NSMutableData'] = (
818    ('__setslice__', NSMutableData__setslice__),
819    ('__setitem__', NSMutableData__setitem__),
820)
821
822
823def __call__(self, *args, **kwds):
824    return _block_call(self, self.__block_signature__, args, kwds)
825
826CLASS_METHODS['NSBlock'] = (
827    ('__call__', __call__),
828)
829
830
831if sys.version_info[0] == 3 or (sys.version_info[0] == 2 and sys.version_info[1] >= 6):
832    import collections
833
834    def all_contained_in(inner, outer):
835        """
836        Return True iff all items in ``inner`` are also in ``outer``.
837        """
838        for v in inner:
839            if v not in outer:
840                return False
841
842        return True
843
844    class nsdict_view (object):
845        __slots__ = ()
846
847        def __eq__(self, other):
848            if not isinstance(other, collections.Set):
849                return NotImplemented
850
851            if len(self) == len(other):
852                return all_contained_in(self, other)
853
854            else:
855                return False
856
857        def __ne__(self, other):
858            if not isinstance(other, collections.Set):
859                return NotImplemented
860
861            if len(self) == len(other):
862                return not all_contained_in(self, other)
863
864            else:
865                return True
866
867        def __lt__(self, other):
868            if not isinstance(other, collections.Set):
869                return NotImplemented
870
871            if len(self) < len(other):
872                return all_contained_in(self, other)
873
874            else:
875                return False
876
877        def __le__(self, other):
878            if not isinstance(other, collections.Set):
879                return NotImplemented
880
881            if len(self) <= len(other):
882                return all_contained_in(self, other)
883
884            else:
885                return False
886
887        def __gt__(self, other):
888            if not isinstance(other, collections.Set):
889                return NotImplemented
890
891            if len(self) > len(other):
892                return all_contained_in(other, self)
893
894            else:
895                return False
896
897        def __ge__(self, other):
898            if not isinstance(other, collections.Set):
899                return NotImplemented
900
901            if len(self) >= len(other):
902                return all_contained_in(other, self)
903
904            else:
905                return False
906
907        def __and__(self, other):
908            if not isinstance(other, collections.Set):
909                return NotImplemented
910            result = set(self)
911            result.intersection_update(other)
912            return result
913
914        def __or__(self, other):
915            if not isinstance(other, collections.Set):
916                return NotImplemented
917            result = set(self)
918            result.update(other)
919            return result
920
921        def __ror__(self, other):
922            if not isinstance(other, collections.Set):
923                return NotImplemented
924            result = set(self)
925            result.update(other)
926            return result
927
928        def __sub__(self, other):
929            if not isinstance(other, collections.Set):
930                return NotImplemented
931            result = set(self)
932            result.difference_update(other)
933            return result
934
935        def __xor__(self, other):
936            if not isinstance(other, collections.Set):
937                return NotImplemented
938            result = set(self)
939            result.symmetric_difference_update(other)
940            return result
941
942    collections.Set.register(nsdict_view)
943
944    class nsdict_keys(nsdict_view):
945        __slots__=('__value')
946        def __init__(self, value):
947            self.__value =  value
948
949        def __repr__(self):
950            keys = list(self.__value)
951            keys.sort()
952
953            return "<nsdict_keys({0})>".format(keys)
954
955
956        def __len__(self):
957            return len(self.__value)
958
959        def __iter__(self):
960            return iter(self.__value)
961
962        def __contains__(self, value):
963            return value in self.__value
964
965    class nsdict_values(nsdict_view):
966        __slots__=('__value')
967        def __init__(self, value):
968            self.__value =  value
969
970        def __repr__(self):
971            values = list(self)
972            values.sort()
973
974            return "<nsdict_values({0})>".format(values)
975
976        def __len__(self):
977            return len(self.__value)
978
979        def __iter__(self):
980            return iter(self.__value.objectEnumerator())
981
982        def __contains__(self, value):
983            for v in iter(self):
984                if value == v:
985                    return True
986            return False
987
988    class nsdict_items(nsdict_view):
989
990        __slots__=('__value')
991
992        def __init__(self, value):
993            self.__value =  value
994
995        def __repr__(self):
996            values = list(self)
997            values.sort()
998
999            return "<nsdict_items({0})>".format(values)
1000
1001        def __len__(self):
1002            return len(self.__value)
1003
1004        def __iter__(self):
1005            for k in self.__value:
1006                yield (k, self.__value[k])
1007
1008        def __contains__(self, value):
1009            for v in iter(self):
1010                if value == v:
1011                    return True
1012            return False
1013
1014    collections.KeysView.register(nsdict_keys)
1015    collections.ValuesView.register(nsdict_values)
1016    collections.ItemsView.register(nsdict_items)
1017
1018    collections.Mapping.register(lookUpClass('NSDictionary'))
1019    collections.MutableMapping.register(lookUpClass('NSMutableDictionary'))
1020
1021
1022
1023    NSDictionary = lookUpClass('NSDictionary')
1024    def nsdict_fromkeys(cls, keys, value=None):
1025        keys = [container_wrap(k) for k in keys]
1026        values = [container_wrap(value)]*len(keys)
1027
1028        return NSDictionary.dictionaryWithObjects_forKeys_(values, keys)
1029
1030    NSMutableDictionary = lookUpClass('NSMutableDictionary')
1031    def nsmutabledict_fromkeys(cls, keys, value=None):
1032        result = NSMutableDictionary.dictionary()
1033        value = container_wrap(value)
1034        for k in keys:
1035            result[container_wrap(k)] = value
1036
1037        return result
1038
1039    def dict_new(cls, args, kwds):
1040        if len(args) == 0:
1041            pass
1042
1043        elif len(args) == 1:
1044            d = dict()
1045            if isinstance(args[0], collections.Mapping):
1046                items = args[0].iteritems()
1047            else:
1048                items = args[0]
1049            for k , v in items:
1050                d[container_wrap(k)] = container_wrap(v)
1051
1052            for k, v in kwds.iteritems():
1053                d[container_wrap(k)] = container_wrap(v)
1054
1055            return cls.dictionaryWithDictionary_(d)
1056
1057        else:
1058            raise TypeError(
1059                    "dict expected at most 1 arguments, got {0}".format(
1060                        len(args)))
1061        if kwds:
1062            d = dict()
1063            for k, v in kwds.iteritems():
1064                d[container_wrap(k)] = container_wrap(v)
1065
1066            return cls.dictionaryWithDictionary_(d)
1067
1068        return cls.dictionary()
1069
1070    def nsdict_new(cls, *args, **kwds):
1071        return dict_new(NSDictionary, args, kwds)
1072
1073    def nsmutabledict_new(cls, *args, **kwds):
1074        return dict_new(NSMutableDictionary, args, kwds)
1075
1076
1077    def nsdict__eq__(self, other):
1078        if not isinstance(other, collections.Mapping):
1079            return False
1080
1081        return self.isEqualToDictionary_(other)
1082
1083    def nsdict__ne__(self, other):
1084        return not nsdict__eq__(self, other)
1085
1086    def nsdict__richcmp__(self, other):
1087        return NotImplemented
1088
1089
1090    if sys.version_info[0] == 3:
1091        CLASS_METHODS['NSDictionary'] = (
1092            ('fromkeys', classmethod(nsdict_fromkeys)),
1093            ('keys', lambda self: nsdict_keys(self)),
1094            ('values', lambda self: nsdict_values(self)),
1095            ('items', lambda self: nsdict_items(self)),
1096        )
1097
1098        CLASS_METHODS['NSMutableDictionary'] = (
1099            ('fromkeys', classmethod(nsmutabledict_fromkeys)),
1100        )
1101
1102    else:
1103        CLASS_METHODS['NSDictionary'] = (
1104            ('fromkeys', classmethod(nsdict_fromkeys)),
1105            ('viewkeys', lambda self: nsdict_keys(self)),
1106            ('viewvalues', lambda self: nsdict_values(self)),
1107            ('viewitems', lambda self: nsdict_items(self)),
1108            ('keys', lambda self: self.allKeys()),
1109            ('items', lambda self: dictItems(self)),
1110            ('values', lambda self: self.allValues()),
1111        )
1112
1113    CLASS_METHODS['NSDictionary'] += (
1114        ('__eq__', nsdict__eq__),
1115        ('__ne__', nsdict__ne__),
1116        ('__lt__', nsdict__richcmp__),
1117        ('__le__', nsdict__richcmp__),
1118        ('__gt__', nsdict__richcmp__),
1119        ('__ge__', nsdict__richcmp__),
1120    )
1121
1122    NSDictionary.__new__ = nsdict_new
1123    NSMutableDictionary.__new__ = nsmutabledict_new
1124
1125    NSMutableDictionary.dictionary()
1126
1127    #FIXME: This shouldn't be necessary
1128
1129NSMutableArray = lookUpClass('NSMutableArray')
1130def nsarray_add(self, other):
1131    result = NSMutableArray.arrayWithArray_(self)
1132    result.extend(other)
1133    return result
1134
1135def nsarray_radd(self, other):
1136    result = NSMutableArray.arrayWithArray_(other)
1137    result.extend(self)
1138    return result
1139
1140def nsarray_mul(self, other):
1141    """
1142    This tries to implement anNSArray * N
1143    somewhat efficently (and definitely more
1144    efficient that repeated appending).
1145    """
1146    result = NSMutableArray.array()
1147
1148    if other <= 0:
1149        return result
1150
1151    n = 1
1152    tmp = self
1153    while other:
1154        if other & n != 0:
1155            result.extend(tmp)
1156            other -= n
1157
1158        if other:
1159            n <<= 1
1160            tmp = tmp.arrayByAddingObjectsFromArray_(tmp)
1161
1162    #for n in xrange(other):
1163        #result.extend(self)
1164    return result
1165
1166
1167
1168def nsarray_new(cls, sequence=None):
1169    if not sequence:
1170        return NSArray.array()
1171
1172    elif isinstance(sequence, (str, unicode)):
1173        return NSArray.arrayWithArray_(list(sequence))
1174
1175    else:
1176        if not isinstance(sequence, (list, tuple)):
1177            # FIXME: teach bridge to treat range and other list-lik
1178            # types correctly
1179            return NSArray.arrayWithArray_(list(sequence))
1180
1181        return NSArray.arrayWithArray_(sequence)
1182
1183def nsmutablearray_new(cls, sequence=None):
1184    if not sequence:
1185        return NSMutableArray.array()
1186
1187    elif isinstance(sequence, (str, unicode)):
1188        return NSMutableArray.arrayWithArray_(list(sequence))
1189
1190    else:
1191        if not isinstance(sequence, (list, tuple)):
1192            # FIXME: teach bridge to treat range and other list-lik
1193            # types correctly
1194            return NSMutableArray.arrayWithArray_(list(sequence))
1195
1196        return NSMutableArray.arrayWithArray_(sequence)
1197
1198CLASS_METHODS['NSArray'] = (
1199    ('__add__', nsarray_add),
1200    ('__radd__', nsarray_radd),
1201    ('__mul__', nsarray_mul),
1202    ('__rmul__', nsarray_mul),
1203)
1204
1205# Force scans to ensure __new__ is set correctly
1206# FIXME: This shouldn't be necessary!
1207NSArray.__new__ = nsarray_new
1208NSMutableArray.__new__ = nsmutablearray_new
1209NSMutableArray.alloc().init()
1210#NSMutableSet.set()
1211
1212NSSet = lookUpClass('NSSet')
1213NSMutableSet = lookUpClass('NSMutableSet')
1214
1215try:
1216    from collections import Set
1217    Set.register(NSSet)
1218except:
1219    Set = (set, frozenset, NSSet)
1220
1221def nsset_isdisjoint(self, other):
1222    for item in self:
1223        if item in other:
1224            return False
1225    return True
1226
1227def nsset_union(self, *other):
1228    result = NSMutableSet()
1229    result.unionSet_(self)
1230    for val in other:
1231        if isinstance(val, Set):
1232            result.unionSet_(val)
1233        else:
1234            result.unionSet_(set(val))
1235    return result
1236
1237def nsset_intersection(self, *others):
1238    if len(others) == 0:
1239        return self.mutableCopy()
1240    result = NSMutableSet()
1241    for item in self:
1242        for o in others:
1243            if item not in o:
1244                break
1245        else:
1246            result.add(item)
1247    return result
1248
1249def nsset_difference(self, *others):
1250    result = self.mutableCopy()
1251
1252    for value in others:
1253        if isinstance(value, Set):
1254            result.minusSet_(value)
1255        else:
1256            result.minusSet_(set(value))
1257
1258    return result
1259
1260def nsset_symmetric_difference(self, other):
1261    result = NSMutableSet()
1262    for item in self:
1263        if item not in other:
1264            result.add(item)
1265    for item in other:
1266        if item not in self:
1267            result.add(item)
1268    return result
1269
1270
1271def nsset__contains__(self, value):
1272    hash(value) # Force error for non-hashable values
1273    return self.containsObject_(value)
1274
1275def nsset__or__(self, other):
1276    if not isinstance(self, Set):
1277        raise TypeError("NSSet|value where value is not a set")
1278    if not isinstance(other, Set):
1279        raise TypeError("NSSet|value where value is not a set")
1280    return nsset_union(self, other)
1281
1282def nsset__ror__(self, other):
1283    if not isinstance(self, Set):
1284        raise TypeError("value|NSSet where value is not a set")
1285    if not isinstance(other, Set):
1286        raise TypeError("value|NSSet where value is not a set")
1287    return nsset_union(other, self)
1288
1289def nsset__and__(self, other):
1290    if not isinstance(self, Set):
1291        raise TypeError("NSSet&value where value is not a set")
1292    if not isinstance(other, Set):
1293        raise TypeError("NSSet&value where value is not a set")
1294    return nsset_intersection(self, other)
1295
1296def nsset__rand__(self, other):
1297    if not isinstance(self, Set):
1298        raise TypeError("value&NSSet where value is not a set")
1299    if not isinstance(other, Set):
1300        raise TypeError("value&NSSet where value is not a set")
1301    return nsset_intersection(other, self)
1302
1303def nsset__sub__(self, other):
1304    if not isinstance(self, Set):
1305        raise TypeError("NSSet-value where value is not a set")
1306    if not isinstance(other, Set):
1307        raise TypeError("NSSet-value where value is not a set")
1308    return nsset_difference(self, other)
1309
1310def nsset_rsub__(self, other):
1311    if not isinstance(self, Set):
1312        raise TypeError("NSSet-value where value is not a set")
1313    if not isinstance(other, Set):
1314        raise TypeError("NSSet-value where value is not a set")
1315    return nsset_difference(other, self)
1316
1317def nsset__xor__(self, other):
1318    if not isinstance(self, Set):
1319        raise TypeError("NSSet-value where value is not a set")
1320    if not isinstance(other, Set):
1321        raise TypeError("NSSet-value where value is not a set")
1322    return nsset_symmetric_difference(other, self)
1323
1324def nsset_issubset(self, other):
1325    if isinstance(other, Set):
1326        return self.isSubsetOfSet_(other)
1327
1328    else:
1329        return self.isSubsetOfSet_(set(other))
1330
1331def nsset__le__(self, other):
1332    if not isinstance(other, Set):
1333        raise TypeError()
1334    return nsset_issubset(self, other)
1335
1336def nsset__eq__(self, other):
1337    if not isinstance(other, Set):
1338        return False
1339
1340    return self.isEqualToSet_(other)
1341
1342def nsset__ne__(self, other):
1343    if not isinstance(other, Set):
1344        return True
1345
1346    return not self.isEqualToSet_(other)
1347
1348def nsset__lt__(self, other):
1349    if not isinstance(other, Set):
1350        raise TypeError()
1351
1352    return (self <= other) and (self != other)
1353
1354def nsset_issuperset(self, other):
1355    if not isinstance(other, Set):
1356        other = set(other)
1357
1358    for item in other:
1359        if item not in self:
1360            return False
1361
1362    return True
1363
1364def nsset__ge__(self, other):
1365    if not isinstance(other, Set):
1366        raise TypeError()
1367    return nsset_issuperset(self, other)
1368
1369def nsset__gt__(self, other):
1370    if not isinstance(other, Set):
1371        raise TypeError()
1372    return (self >= other) and (self != other)
1373
1374if sys.version_info[0] == 2:
1375    def nsset__cmp__(self, other):
1376        try:
1377            if self < other:
1378                return -1
1379            elif self == other:
1380                return 0
1381            else:
1382                return 1
1383        except TypeError:
1384            return cmp(id(self), id(other))
1385
1386def nsset__length_hint__(self):
1387    return len(self)
1388
1389def nsset_update(self, *others):
1390    for other in others:
1391        if isinstance(other, Set):
1392            self.unionSet_(other)
1393        else:
1394            self.unionSet_(set(other))
1395
1396def nsset_intersection_update(self, *others):
1397    for other in others:
1398        if isinstance(other, Set):
1399            self.intersectSet_(other)
1400        else:
1401            self.intersectSet_(set(other))
1402
1403def nsset_difference_update(self, *others):
1404    for other in others:
1405        if isinstance(other, Set):
1406            self.minusSet_(other)
1407        else:
1408            self.minusSet_(set(other))
1409
1410def nsset_symmetric_difference_update(self, other):
1411    toadd = set()
1412    toremove = set()
1413
1414    if isinstance(other, Set):
1415        totest = other
1416    else:
1417        totest = set(other)
1418
1419    for value in self:
1420        if value in totest:
1421            toremove.add(value)
1422    for value in totest:
1423        if value not in self:
1424            toadd.add(value)
1425
1426    self.minusSet_(toremove)
1427    self.unionSet_(toadd)
1428
1429def nsset_pop(self):
1430    if len(self) == 0:
1431        raise KeyError()
1432
1433    v = self.anyObject()
1434    self.removeObject_(v)
1435    return container_unwrap(v, KeyError)
1436
1437def nsset_remove(self, value):
1438    hash(value)
1439    value = container_wrap(value)
1440    if value not in self:
1441        raise KeyError(value)
1442    self.removeObject_(value)
1443
1444def nsset_discard(self, value):
1445    hash(value)
1446    self.removeObject_(container_wrap(value))
1447
1448def nsset_add(self, value):
1449    hash(value)
1450    self.addObject_(container_wrap(value))
1451
1452class nsset__iter__ (object):
1453    def __init__(self, value):
1454        self._size = len(value)
1455        self._enum = value.objectEnumerator()
1456
1457    def __length_hint__(self):
1458        return self._size
1459
1460    def __iter__(self):
1461        return self
1462
1463    def next(self):
1464        self._size -= 1
1465        return container_unwrap(self._enum.nextObject(), StopIteration)
1466
1467
1468CLASS_METHODS['NSSet'] = (
1469    ('__iter__', lambda self: nsset__iter__(self)),
1470    ('__length_hint__', nsset__length_hint__),
1471    ('__contains__',  nsset__contains__),
1472    ('isdisjoint',  nsset_isdisjoint),
1473    ('union',  nsset_union),
1474    ('intersection',  nsset_intersection),
1475    ('difference',  nsset_difference),
1476    ('symmetric_difference',  nsset_symmetric_difference),
1477    ('issubset', nsset_issubset),
1478    ('__eq__', nsset__eq__),
1479    ('__ne__', nsset__ne__),
1480    ('__le__', nsset__le__),
1481    ('__lt__', nsset__lt__),
1482    ('issuperset', nsset_issuperset),
1483    ('__ge__', nsset__ge__),
1484    ('__gt__', nsset__gt__),
1485    ('__or__', nsset__or__),
1486    ('__ror__', nsset__ror__),
1487    ('__and__', nsset__and__),
1488    ('__rand__', nsset__rand__),
1489    ('__xor__', nsset__xor__),
1490    ('__rxor__', nsset__xor__),
1491    ('__sub__', nsset__sub__),
1492)
1493
1494if sys.version_info[0] == 2:
1495    CLASS_METHODS['NSSet'] += (
1496        ('__cmp__', 'nsset__cmp__'),
1497    )
1498
1499CLASS_METHODS['NSMutableSet'] = (
1500    ('add',  nsset_add),
1501    ('remove',  nsset_remove),
1502    ('discard',  nsset_discard),
1503    ('update', nsset_update),
1504    ('intersection_update', nsset_intersection_update),
1505    ('difference_update', nsset_difference_update),
1506    ('symmetric_difference_update', nsset_symmetric_difference_update),
1507    ('clear', lambda self: self.removeAllObjects()),
1508    ('pop', nsset_pop),
1509)
1510
1511def nsset_new(cls, sequence=None):
1512    if not sequence:
1513        return NSSet.set()
1514
1515    if isinstance(sequence, (NSSet, set, frozenset)):
1516        return NSSet.set().setByAddingObjectsFromSet_(sequence)
1517
1518    else:
1519        return NSSet.set().setByAddingObjectsFromSet_(set(sequence))
1520
1521def nsmutableset_new(cls, sequence=None):
1522    if not sequence:
1523        value = NSMutableSet.set()
1524
1525    elif isinstance(sequence, (NSSet, set, frozenset)):
1526        value = NSMutableSet.set()
1527        value.unionSet_(sequence)
1528
1529    else:
1530        value = NSMutableSet.set()
1531        value.unionSet_(set(sequence))
1532
1533    return value
1534
1535NSSet.__new__ = nsset_new
1536NSMutableSet.__new__ = nsmutableset_new
1537
1538NSMutableSet.alloc().init()
1539