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