containers.py revision d0332953cda33fb4f8e24ebff9c49159b69c43d6
1# Protocol Buffers - Google's data interchange format
2# Copyright 2008 Google Inc.  All rights reserved.
3# http://code.google.com/p/protobuf/
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9#     * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#     * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15#     * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31"""Contains container classes to represent different protocol buffer types.
32
33This file defines container classes which represent categories of protocol
34buffer field types which need extra maintenance. Currently these categories
35are:
36  - Repeated scalar fields - These are all repeated fields which aren't
37    composite (e.g. they are of simple types like int32, string, etc).
38  - Repeated composite fields - Repeated fields which are composite. This
39    includes groups and nested messages.
40"""
41
42__author__ = 'petar@google.com (Petar Petrov)'
43
44
45class BaseContainer(object):
46
47  """Base container class."""
48
49  # Minimizes memory usage and disallows assignment to other attributes.
50  __slots__ = ['_message_listener', '_values']
51
52  def __init__(self, message_listener):
53    """
54    Args:
55      message_listener: A MessageListener implementation.
56        The RepeatedScalarFieldContainer will call this object's
57        Modified() method when it is modified.
58    """
59    self._message_listener = message_listener
60    self._values = []
61
62  def __getitem__(self, key):
63    """Retrieves item by the specified key."""
64    return self._values[key]
65
66  def __len__(self):
67    """Returns the number of elements in the container."""
68    return len(self._values)
69
70  def __ne__(self, other):
71    """Checks if another instance isn't equal to this one."""
72    # The concrete classes should define __eq__.
73    return not self == other
74
75  def __repr__(self):
76    return repr(self._values)
77
78
79class RepeatedScalarFieldContainer(BaseContainer):
80
81  """Simple, type-checked, list-like container for holding repeated scalars."""
82
83  # Disallows assignment to other attributes.
84  __slots__ = ['_type_checker']
85
86  def __init__(self, message_listener, type_checker):
87    """
88    Args:
89      message_listener: A MessageListener implementation.
90        The RepeatedScalarFieldContainer will call this object's
91        Modified() method when it is modified.
92      type_checker: A type_checkers.ValueChecker instance to run on elements
93        inserted into this container.
94    """
95    super(RepeatedScalarFieldContainer, self).__init__(message_listener)
96    self._type_checker = type_checker
97
98  def append(self, value):
99    """Appends an item to the list. Similar to list.append()."""
100    self._type_checker.CheckValue(value)
101    self._values.append(value)
102    if not self._message_listener.dirty:
103      self._message_listener.Modified()
104
105  def insert(self, key, value):
106    """Inserts the item at the specified position. Similar to list.insert()."""
107    self._type_checker.CheckValue(value)
108    self._values.insert(key, value)
109    if not self._message_listener.dirty:
110      self._message_listener.Modified()
111
112  def extend(self, elem_seq):
113    """Extends by appending the given sequence. Similar to list.extend()."""
114    if not elem_seq:
115      return
116
117    new_values = []
118    for elem in elem_seq:
119      self._type_checker.CheckValue(elem)
120      new_values.append(elem)
121    self._values.extend(new_values)
122    self._message_listener.Modified()
123
124  def MergeFrom(self, other):
125    """Appends the contents of another repeated field of the same type to this
126    one. We do not check the types of the individual fields.
127    """
128    self._values.extend(other._values)
129    self._message_listener.Modified()
130
131  def remove(self, elem):
132    """Removes an item from the list. Similar to list.remove()."""
133    self._values.remove(elem)
134    self._message_listener.Modified()
135
136  def __setitem__(self, key, value):
137    """Sets the item on the specified position."""
138    self._type_checker.CheckValue(value)
139    self._values[key] = value
140    self._message_listener.Modified()
141
142  def __getslice__(self, start, stop):
143    """Retrieves the subset of items from between the specified indices."""
144    return self._values[start:stop]
145
146  def __setslice__(self, start, stop, values):
147    """Sets the subset of items from between the specified indices."""
148    new_values = []
149    for value in values:
150      self._type_checker.CheckValue(value)
151      new_values.append(value)
152    self._values[start:stop] = new_values
153    self._message_listener.Modified()
154
155  def __delitem__(self, key):
156    """Deletes the item at the specified position."""
157    del self._values[key]
158    self._message_listener.Modified()
159
160  def __delslice__(self, start, stop):
161    """Deletes the subset of items from between the specified indices."""
162    del self._values[start:stop]
163    self._message_listener.Modified()
164
165  def __eq__(self, other):
166    """Compares the current instance with another one."""
167    if self is other:
168      return True
169    # Special case for the same type which should be common and fast.
170    if isinstance(other, self.__class__):
171      return other._values == self._values
172    # We are presumably comparing against some other sequence type.
173    return other == self._values
174
175
176class RepeatedCompositeFieldContainer(BaseContainer):
177
178  """Simple, list-like container for holding repeated composite fields."""
179
180  # Disallows assignment to other attributes.
181  __slots__ = ['_message_descriptor']
182
183  def __init__(self, message_listener, message_descriptor):
184    """
185    Note that we pass in a descriptor instead of the generated directly,
186    since at the time we construct a _RepeatedCompositeFieldContainer we
187    haven't yet necessarily initialized the type that will be contained in the
188    container.
189
190    Args:
191      message_listener: A MessageListener implementation.
192        The RepeatedCompositeFieldContainer will call this object's
193        Modified() method when it is modified.
194      message_descriptor: A Descriptor instance describing the protocol type
195        that should be present in this container.  We'll use the
196        _concrete_class field of this descriptor when the client calls add().
197    """
198    super(RepeatedCompositeFieldContainer, self).__init__(message_listener)
199    self._message_descriptor = message_descriptor
200
201  def add(self):
202    new_element = self._message_descriptor._concrete_class()
203    new_element._SetListener(self._message_listener)
204    self._values.append(new_element)
205    if not self._message_listener.dirty:
206      self._message_listener.Modified()
207    return new_element
208
209  def MergeFrom(self, other):
210    """Appends the contents of another repeated field of the same type to this
211    one, copying each individual message.
212    """
213    message_class = self._message_descriptor._concrete_class
214    listener = self._message_listener
215    values = self._values
216    for message in other._values:
217      new_element = message_class()
218      new_element._SetListener(listener)
219      new_element.MergeFrom(message)
220      values.append(new_element)
221    listener.Modified()
222
223  def __getslice__(self, start, stop):
224    """Retrieves the subset of items from between the specified indices."""
225    return self._values[start:stop]
226
227  def __delitem__(self, key):
228    """Deletes the item at the specified position."""
229    del self._values[key]
230    self._message_listener.Modified()
231
232  def __delslice__(self, start, stop):
233    """Deletes the subset of items from between the specified indices."""
234    del self._values[start:stop]
235    self._message_listener.Modified()
236
237  def __eq__(self, other):
238    """Compares the current instance with another one."""
239    if self is other:
240      return True
241    if not isinstance(other, self.__class__):
242      raise TypeError('Can only compare repeated composite fields against '
243                      'other repeated composite fields.')
244    return self._values == other._values
245