1# -*- test-case-name: foolscap.test.test_banana -*-
2
3from __future__ import print_function
4from twisted.internet.defer import Deferred
5from foolscap.tokens import Violation
6from foolscap.slicer import BaseUnslicer
7from foolscap.slicers.list import ListSlicer
8from foolscap.constraint import OpenerConstraint, Any, IConstraint
9from foolscap.util import AsyncAND
10
11
12class TupleSlicer(ListSlicer):
13    opentype = ("tuple",)
14    slices = tuple
15
16class TupleUnslicer(BaseUnslicer):
17    opentype = ("tuple",)
18
19    debug = False
20    constraints = None
21
22    def setConstraint(self, constraint):
23        if isinstance(constraint, Any):
24            return
25        assert isinstance(constraint, TupleConstraint)
26        self.constraints = constraint.constraints
27
28    def start(self, count):
29        self.list = []
30        # indices of .list which are unfilled because of children that could
31        # not yet be referenced
32        self.num_unreferenceable_children = 0
33        self.count = count
34        if self.debug:
35            print("%s[%d].start with %s" % (self, self.count, self.list))
36        self.finished = False
37        self.deferred = Deferred()
38        self.protocol.setObject(count, self.deferred)
39        self._ready_deferreds = []
40
41    def checkToken(self, typebyte, size):
42        if self.constraints == None:
43            return
44        if len(self.list) >= len(self.constraints):
45            raise Violation("the tuple is full")
46        self.constraints[len(self.list)].checkToken(typebyte, size)
47
48    def doOpen(self, opentype):
49        where = len(self.list)
50        if self.constraints != None:
51            if where >= len(self.constraints):
52                raise Violation("the tuple is full")
53            self.constraints[where].checkOpentype(opentype)
54        unslicer = self.open(opentype)
55        if unslicer:
56            if self.constraints != None:
57                unslicer.setConstraint(self.constraints[where])
58        return unslicer
59
60    def update(self, obj, index):
61        if self.debug:
62            print("%s[%d].update: [%d]=%s" % (self, self.count, index, obj))
63        self.list[index] = obj
64        self.num_unreferenceable_children -= 1
65        if self.finished:
66            self.checkComplete()
67        return obj
68
69    def receiveChild(self, obj, ready_deferred=None):
70        if ready_deferred:
71            self._ready_deferreds.append(ready_deferred)
72        if isinstance(obj, Deferred):
73            obj.addCallback(self.update, len(self.list))
74            obj.addErrback(self.explode)
75            self.num_unreferenceable_children += 1
76            self.list.append("placeholder")
77        else:
78            self.list.append(obj)
79
80    def checkComplete(self):
81        if self.debug:
82            print("%s[%d].checkComplete: %d pending" % \
83                  (self, self.count, self.num_unreferenceable_children))
84        if self.num_unreferenceable_children:
85            # not finished yet, we'll fire our Deferred when we are
86            if self.debug:
87                print(" not finished yet")
88            return
89
90        # list is now complete. We can finish.
91        return self.complete()
92
93    def complete(self):
94        ready_deferred = None
95        if self._ready_deferreds:
96            ready_deferred = AsyncAND(self._ready_deferreds)
97
98        t = tuple(self.list)
99        if self.debug:
100            print(" finished! tuple:%s{%s}" % (t, id(t)))
101        self.protocol.setObject(self.count, t)
102        self.deferred.callback(t)
103        return t, ready_deferred
104
105    def receiveClose(self):
106        if self.debug:
107            print("%s[%d].receiveClose" % (self, self.count))
108        self.finished = 1
109
110        if self.num_unreferenceable_children:
111            # not finished yet, we'll fire our Deferred when we are
112            if self.debug:
113                print(" not finished yet")
114            ready_deferred = None
115            if self._ready_deferreds:
116                ready_deferred = AsyncAND(self._ready_deferreds)
117            return self.deferred, ready_deferred
118
119        # the list is already complete
120        return self.complete()
121
122    def describe(self):
123        return "[%d]" % len(self.list)
124
125
126class TupleConstraint(OpenerConstraint):
127    opentypes = [("tuple",)]
128    name = "TupleConstraint"
129
130    def __init__(self, *elemConstraints):
131        self.constraints = [IConstraint(e) for e in elemConstraints]
132    def checkObject(self, obj, inbound):
133        if not isinstance(obj, tuple):
134            raise Violation("not a tuple")
135        if len(obj) != len(self.constraints):
136            raise Violation("wrong size tuple")
137        for i in range(len(self.constraints)):
138            self.constraints[i].checkObject(obj[i], inbound)
139