Package pyamf :: Module amf3

[hide private]
[frames] | no frames]

# Source Code for Module pyamf.amf3

       1  # -*- coding: utf-8 -*-
       2  #
       3  # Copyright (c) 2007-2009 The PyAMF Project.
       4  # See LICENSE for details.
       5
       6  """
       7  AMF3 implementation.
       8
       9  C{AMF3} is the default serialization for
      10  U{ActionScript<http://en.wikipedia.org/wiki/ActionScript>} 3.0 and provides
      11  various advantages over L{AMF0<pyamf.amf0>}, which is used for ActionScript 1.0
      12  and 2.0. It adds support for sending C{int} and C{uint} objects as integers and
      13  supports data types that are available only in ActionScript 3.0, such as
      14  L{ByteArray} and L{ArrayCollection}.
      15
      16  @see: U{Official AMF3 Specification in English (external)
      17  <http://opensource.adobe.com/wiki/download/attachments/1114283/amf3_spec_121207.pdf>}
      18  @see: U{Official AMF3 Specification in Japanese (external)
      19  <http://opensource.adobe.com/wiki/download/attachments/1114283/JP_amf3_spec_121207.pdf>}
      20  @see: U{AMF3 documentation on OSFlash (external)
      21  <http://osflash.org/documentation/amf3>}
      22
      23  @since: 0.1.0
      24  """
      25
      26  import types, datetime, zlib
      27
      28  import pyamf
      29  from pyamf import util
      30  from pyamf.flex import ObjectProxy, ArrayCollection
      31
      32  #: If True encode/decode lists/tuples to ArrayCollections
      33  #: and dicts to ObjectProxy
      34  use_proxies_default = False
      35
      36  try:
      37      set()
      38  except NameError:
      39      from sets import Set as set
      40
      41 -class ASTypes:
      42      """
      43      All AMF3 data types used in ActionScript 3.0.
      44
      45      AMF represents ActionScript objects by a single byte representing type, and
      46      then by a type-specific byte array that may be of fixed length, may contain
      47      length information, or may come with its own end code.
      48
      49      @see: U{AMF3 data types on OSFlash (external)
      50      <http://osflash.org/documentation/amf3#data_types>}
      51      """
      52      #: The undefined type is represented by the undefined type marker.
      53      #: No further information is encoded for this value.
      54      UNDEFINED  = 0x00
      55      #: The undefined type is represented by the undefined type marker.
      56      #: No further information is encoded for this value.
      57      NULL       = 0x01
      58      #: The false type is represented by the false type marker and is
      59      #: used to encode a Boolean value of C{false}. No further information
      60      #: is encoded for this value.
      61      #: @note: In ActionScript 3.0 the concept of a primitive and Object
      62      #: form of Boolean does not exist.
      63      BOOL_FALSE = 0x02
      64      #: The true type is represented by the true type marker and is
      65      #: used to encode a Boolean value of C{true}. No further information
      66      #: is encoded for this value.
      67      #: @note: In ActionScript 3.0 the concept of a primitive and Object
      68      #: form of Boolean does not exist.
      69      BOOL_TRUE  = 0x03
      70      #: In AMF 3 integers are serialized using a variable length unsigned
      71      #: 29-bit integer.
      72      #: @see: U{Parsing Integers on OSFlash (external)
      73      #: <http://osflash.org/documentation/amf3/parsing_integers>}
      74      INTEGER    = 0x04
      75      #: This type is used to encode an ActionScript Number
      76      #: or an ActionScript C{int} of value greater than or equal to 2^28
      77      #: or an ActionScript uint of value greater than or equal to 2^29.
      78      #: The encoded value is is always an 8 byte IEEE-754 double precision
      79      #: floating point value in network byte order (sign bit in low memory).
      80      #: The AMF 3 number type is encoded in the same manner as the
      81      #: AMF 0 L{Number<pyamf.amf0.ASTypes.NUMBER>} type.
      82      NUMBER     = 0x05
      83      #: ActionScript String values are represented using a single string
      84      #: type in AMF 3 - the concept of string and long string types from
      85      #: AMF 0 is not used. Strings can be sent as a reference to a previously
      86      #: occurring String by using an index to the implicit string reference
      87      #: table.
      88      #: Strings are encoding using UTF-8 - however the header may either
      89      #: describe a string literal or a string reference.
      90      STRING     = 0x06
      91      #: ActionScript 3.0 introduced a new XML type however the legacy
      92      #: C{XMLDocument} type from ActionScript 1.0 and 2.0.is retained
      93      #: in the language as C{flash.xml.XMLDocument}. Similar to AMF 0, the
      94      #: structure of an C{XMLDocument} needs to be flattened into a string
      95      #: representation for serialization. As with other strings in AMF,
      96      #: the content is encoded in UTF-8. XMLDocuments can be sent as a reference
      97      #: to a previously occurring C{XMLDocument} instance by using an index to
      98      #: the implicit object reference table.
      99      #: @see: U{OSFlash documentation (external)
     100      #: <http://osflash.org/documentation/amf3#x07_-_xml_legacy_flash.xml.xmldocument_class>}
     101      XML        = 0x07
     102      #: In AMF 3 an ActionScript Date is serialized simply as the number of
     103      #: milliseconds elapsed since the epoch of midnight, 1st Jan 1970 in the
     104      #: UTC time zone. Local time zone information is not sent.
     105      DATE       = 0x08
     106      #: ActionScript Arrays are described based on the nature of their indices,
     107      #: i.e. their type and how they are positioned in the Array.
     108      ARRAY      = 0x09
     109      #: A single AMF 3 type handles ActionScript Objects and custom user classes.
     110      OBJECT     = 0x0a
     111      #: ActionScript 3.0 introduces a new top-level XML class that supports
     112      #: U{E4X<http://en.wikipedia.org/wiki/E4X>} syntax.
     113      #: For serialization purposes the XML type needs to be flattened into a
     114      #: string representation. As with other strings in AMF, the content is
     115      #: encoded using UTF-8.
     116      XMLSTRING  = 0x0b
     117      #: ActionScript 3.0 introduces the L{ByteArray} type to hold an Array
     118      #: of bytes. AMF 3 serializes this type using a variable length encoding
     119      #: 29-bit integer for the byte-length prefix followed by the raw bytes
     120      #: of the L{ByteArray}.
     121      #: @see: U{Parsing ByteArrays on OSFlash (external)
     122      #: <http://osflash.org/documentation/amf3/parsing_byte_arrays>}
     123      BYTEARRAY  = 0x0c
     124
     125  #: List of available ActionScript types in AMF3.
     126  ACTIONSCRIPT_TYPES = []
     127
     128  for x in ASTypes.__dict__:
     129      if not x.startswith('_'):
     130          ACTIONSCRIPT_TYPES.append(ASTypes.__dict__[x])
     131  del x
     132
     133  #: Reference bit.
     134  REFERENCE_BIT = 0x01
     135
     136 -class ObjectEncoding:
     137      """
     138      AMF object encodings.
     139      """
     140      #: Property list encoding.
     141      #: The remaining integer-data represents the number of class members that
     142      #: exist. The property names are read as string-data. The values are then
     143      #: read as AMF3-data.
     144      STATIC = 0x00
     145
     146      #: Externalizable object.
     147      #: What follows is the value of the "inner" object, including type code.
     148      #: This value appears for objects that implement IExternalizable, such as
     149      #: L{ArrayCollection} and L{ObjectProxy}.
     150      EXTERNAL = 0x01
     151
     152      #: Name-value encoding.
     153      #: The property names and values are encoded as string-data followed by
     154      #: AMF3-data until there is an empty string property name. If there is a
     155      #: class-def reference there are no property names and the number of values
     156      #: is equal to the number of properties in the class-def.
     157      DYNAMIC = 0x02
     158
     159      #: Proxy object.
     160      PROXY = 0x03
     161
     162 -class DataOutput(object):
     163      """
     164      I am a C{StringIO} type object containing byte data from the AMF stream.
     165      ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
     166      the manipulation of raw data in the form of an Array of bytes.
     167      I provide a set of methods for writing binary data with ActionScript 3.0.
     168
     169      This class is the I/O counterpart to the L{DataInput} class, which reads
     170      binary data.
     171
     172      @see: U{IDataOutput on Livedocs (external)
     173      <http://livedocs.adobe.com/flex/201/langref/flash/utils/IDataOutput.html>}
     174      """
     175 -    def __init__(self, encoder):
     176          """
     177          @param encoder: Encoder containing the stream.
     178          @type encoder: L{amf3.Encoder<pyamf.amf3.Encoder>}
     179          """
     180          assert isinstance(encoder, Encoder)
     181
     182          self.encoder = encoder
     183          self.stream = encoder.stream
     184
     185 -    def writeBoolean(self, value):
     186          """
     187          Writes a Boolean value.
     188
     189          @type value: C{bool}
     190          @param value: A C{Boolean} value determining which byte is written.
     191          If the parameter is C{True}, C{1} is written; if C{False}, C{0} is
     192          written.
     193
     194          @raise ValueError: Non-boolean value found.
     195          """
     196          if isinstance(value, bool):
     197              if value is True:
     198                  self.stream.write_uchar(1)
     199              else:
     200                  self.stream.write_uchar(0)
     201          else:
     202              raise ValueError("Non-boolean value found")
     203
     204 -    def writeByte(self, value):
     205          """
     206          Writes a byte.
     207
     208          @type value: C{int}
     209          """
     210          self.stream.write_char(value)
     211
     212 -    def writeDouble(self, value):
     213          """
     214          Writes an IEEE 754 double-precision (64-bit) floating
     215          point number.
     216
     217          @type value: C{number}
     218          """
     219          self.stream.write_double(value)
     220
     221 -    def writeFloat(self, value):
     222          """
     223          Writes an IEEE 754 single-precision (32-bit) floating
     224          point number.
     225
     226          @type value: C{float}
     227          """
     228          self.stream.write_float(value)
     229
     230 -    def writeInt(self, value):
     231          """
     232          Writes a 32-bit signed integer.
     233
     234          @type value: C{int}
     235          """
     236          self.stream.write_long(value)
     237
     238 -    def writeMultiByte(self, value, charset):
     239          """
     240          Writes a multibyte string to the datastream using the
     241          specified character set.
     242
     243          @type value: C{str}
     244          @param value: The string value to be written.
     245          @type charset: C{str}
     246          @param charset: The string denoting the character
     247          set to use. Possible character set strings include
     248          C{shift-jis}, C{cn-gb}, C{iso-8859-1} and others.
     249          @see: U{Supported character sets on Livedocs (external)
     250          <http://livedocs.adobe.com/flex/201/langref/charset-codes.html>}
     251          """
     252          self.stream.write(unicode(value).encode(charset))
     253
     254 -    def writeObject(self, value, use_references=True):
     255          """
     256          Writes an object to data stream in AMF serialized format.
     257
     258          @param value: The object to be serialized.
     259          @type use_references: C{bool}
     260          @param use_references:
     261          """
     262          self.encoder.writeElement(value, use_references)
     263
     264 -    def writeShort(self, value):
     265          """
     266          Writes a 16-bit integer.
     267
     268          @type value: C{int}
     269          @param value: A byte value as an integer.
     270          """
     271          self.stream.write_short(value)
     272
     273 -    def writeUnsignedInt(self, value):
     274          """
     275          Writes a 32-bit unsigned integer.
     276
     277          @type value: C{int}
     278          @param value: A byte value as an unsigned integer.
     279          """
     280          self.stream.write_ulong(value)
     281
     282 -    def writeUTF(self, value):
     283          """
     284          Writes a UTF-8 string to the data stream.
     285
     286          The length of the UTF-8 string in bytes is written first,
     287          as a 16-bit integer, followed by the bytes representing the
     288          characters of the string.
     289
     290          @type value: C{str}
     291          @param value: The string value to be written.
     292          """
     293          if not isinstance(value, unicode):
     294              value = unicode(value, 'utf8')
     295
     296          buf = util.BufferedByteStream()
     297          buf.write_utf8_string(value)
     298          bytes = buf.getvalue()
     299
     300          self.stream.write_ushort(len(bytes))
     301          self.stream.write(bytes)
     302
     303 -    def writeUTFBytes(self, value):
     304          """
     305          Writes a UTF-8 string. Similar to L{writeUTF}, but does
     306          not prefix the string with a 16-bit length word.
     307
     308          @type value: C{str}
     309          @param value: The string value to be written.
     310          """
     311          val = None
     312
     313          if isinstance(value, unicode):
     314              val = value
     315          else:
     316              val = unicode(value, 'utf8')
     317
     318          self.stream.write_utf8_string(val)
     319
     320 -class DataInput(object):
     321      """
     322      I provide a set of methods for reading binary data with ActionScript 3.0.
     323
     324      This class is the I/O counterpart to the L{DataOutput} class,
     325      which writes binary data.
     326
     327      @see: U{IDataInput on Livedocs (external)
     328      <http://livedocs.adobe.com/flex/201/langref/flash/utils/IDataInput.html>}
     329      """
     330 -    def __init__(self, decoder):
     331          """
     332          @param decoder: AMF3 decoder containing the stream.
     333          @type decoder: L{amf3.Decoder<pyamf.amf3.Decoder>}
     334          """
     335          assert isinstance(decoder, Decoder)
     336
     337          self.decoder = decoder
     338          self.stream = decoder.stream
     339
     340 -    def readBoolean(self):
     341          """
     342          Read C{Boolean}.
     343
     344          @raise ValueError: Error reading Boolean.
     345          @rtype: C{bool}
     346          @return: A Boolean value, C{True} if the byte
     347          is nonzero, C{False} otherwise.
     348          """
     349          byte = self.stream.read(1)
     350
     351          if byte == '\x00':
     352              return False
     353          elif byte == '\x01':
     354              return True
     355          else:
     356              raise ValueError("Error reading boolean")
     357
     358 -    def readByte(self):
     359          """
     360          Reads a signed byte.
     361
     362          @rtype: C{int}
     363          @return: The returned value is in the range -128 to 127.
     364          """
     365          return self.stream.read_char()
     366
     367 -    def readDouble(self):
     368          """
     369          Reads an IEEE 754 double-precision floating point number from the
     370          data stream.
     371
     372          @rtype: C{number}
     373          @return: An IEEE 754 double-precision floating point number.
     374          """
     375          return self.stream.read_double()
     376
     377 -    def readFloat(self):
     378          """
     379          Reads an IEEE 754 single-precision floating point number from the
     380          data stream.
     381
     382          @rtype: C{number}
     383          @return: An IEEE 754 single-precision floating point number.
     384          """
     385          return self.stream.read_float()
     386
     387 -    def readInt(self):
     388          """
     389          Reads a signed 32-bit integer from the data stream.
     390
     391          @rtype: C{int}
     392          @return: The returned value is in the range -2147483648 to 2147483647.
     393          """
     394          return self.stream.read_long()
     395
     396 -    def readMultiByte(self, length, charset):
     397          """
     398          Reads a multibyte string of specified length from the data stream
     399          using the specified character set.
     400
     401          @type length: C{int}
     402          @param length: The number of bytes from the data stream to read.
     403          @type charset: C{str}
     404          @param charset: The string denoting the character set to use.
     405
     406          @rtype: C{str}
     407          @return: UTF-8 encoded string.
     408          """
     409          #FIXME nick: how to work out the code point byte size (on the fly)?
     410          bytes = self.stream.read(length)
     411
     412          return unicode(bytes, charset)
     413
     414 -    def readObject(self):
     415          """
     416          Reads an object from the data stream.
     417
     418          @return: The deserialized object.
     419          """
     420          return self.decoder.readElement()
     421
     422 -    def readShort(self):
     423          """
     424          Reads a signed 16-bit integer from the data stream.
     425
     426          @rtype: C{uint}
     427          @return: The returned value is in the range -32768 to 32767.
     428          """
     429          return self.stream.read_short()
     430
     431 -    def readUnsignedByte(self):
     432          """
     433          Reads an unsigned byte from the data stream.
     434
     435          @rtype: C{uint}
     436          @return: The returned value is in the range 0 to 255.
     437          """
     438          return self.stream.read_uchar()
     439
     440 -    def readUnsignedInt(self):
     441          """
     442          Reads an unsigned 32-bit integer from the data stream.
     443
     444          @rtype: C{uint}
     445          @return: The returned value is in the range 0 to 4294967295.
     446          """
     447          return self.stream.read_ulong()
     448
     449 -    def readUnsignedShort(self):
     450          """
     451          Reads an unsigned 16-bit integer from the data stream.
     452
     453          @rtype: C{uint}
     454          @return: The returned value is in the range 0 to 65535.
     455          """
     456          return self.stream.read_ushort()
     457
     458 -    def readUTF(self):
     459          """
     460          Reads a UTF-8 string from the data stream.
     461
     462          The string is assumed to be prefixed with an unsigned
     463          short indicating the length in bytes.
     464
     465          @rtype: C{str}
     466          @return: A UTF-8 string produced by the byte
     467          representation of characters.
     468          """
     469          length = self.stream.read_ushort()
     470          return self.stream.read_utf8_string(length)
     471
     472 -    def readUTFBytes(self, length):
     473          """
     474          Reads a sequence of C{length} UTF-8 bytes from the data
     475          stream and returns a string.
     476
     477          @type length: C{int}
     478          @param length: The number of bytes from the data stream to read.
     479          @rtype: C{str}
     480          @return: A UTF-8 string produced by the byte representation of
     481          characters of specified C{length}.
     482          """
     483          return self.readMultiByte(length, 'utf-8')
     484
     485 -class ByteArray(util.BufferedByteStream, DataInput, DataOutput):
     486      """
     487      I am a C{StringIO} type object containing byte data from the AMF stream.
     488      ActionScript 3.0 introduced the C{flash.utils.ByteArray} class to support
     489      the manipulation of raw data in the form of an Array of bytes.
     490
     491      Supports C{zlib} compression.
     492
     493      Possible uses of the C{ByteArray} class:
     494       - Creating a custom protocol to connect to a client.
     495       - Writing your own AMF/Remoting packet.
     496       - Optimizing the size of your data by using custom data types.
     497
     498      @see: U{ByteArray on Livedocs (external)
     499      <http://livedocs.adobe.com/flex/201/langref/flash/utils/ByteArray.html>}
     500      """
     501 -    def __init__(self, *args, **kwargs):
     502          self.context = kwargs.pop('context', Context())
     503
     504          util.BufferedByteStream.__init__(self, *args, **kwargs)
     505          DataInput.__init__(self, Decoder(self, self.context))
     506          DataOutput.__init__(self, Encoder(self, self.context))
     507
     508          self.compressed = False
     509
     510 -    def __cmp__(self, other):
     511          if isinstance(other, ByteArray):
     512              return cmp(self.getvalue(), other.getvalue())
     513
     514          return cmp(self.getvalue(), other)
     515
     516 -    def __str__(self):
     517          buf = self.getvalue()
     518
     519          if self.compressed:
     520              buf = zlib.compress(buf)
     521              #FIXME nick: hacked
     522              buf = buf[0] + '\xda' + buf[2:]
     523
     524          return buf
     525
     526 -    def compress(self):
     527          """
     528          Forces compression of the underlying stream.
     529          """
     530          self.compressed = True
     531
     532  pyamf.register_class(ByteArray, metadata=['amf3'])
     533
     534 -class ClassDefinition(object):
     535      """
     536      I contain meta relating to the class definition.
     537
     538      @ivar alias: The alias to this class definition. If this value is C{None},
     539          or an empty string, the class is considered to be anonymous.
     540      @type alias: L{ClassAlias<pyamf.ClassAlias>}
     541      @ivar encoding: The type of encoding to use when serializing the object.
     542      @type encoding: C{int}
     543      """
     544 -    def __init__(self, alias, encoding=ObjectEncoding.DYNAMIC):
     545          if alias in (None, ''):
     546              self.alias = None
     547          elif isinstance(alias, pyamf.ClassAlias):
     548              self.alias = alias
     549          else:
     550              self.alias = pyamf.get_class_alias(alias)
     551
     552          self.encoding = encoding
     553
     554 -    def _get_name(self):
     555          if self.alias is None:
     556              # anonymous class
     557              return ''
     558
     559          if 'anonymous' in self.alias.metadata:
     560              return ''
     561
     562          return str(self.alias)
     563
     564      name = property(_get_name)
     565
     566 -    def _getClass(self):
     567          """
     568          If C{alias} is C{None}, an L{anonymous class<pyamf.ASObject>} is
     569          returned, otherwise the class is loaded externally.
     570          """
     571          if self.alias in (None, ''):
     572              # anonymous class
     573              return pyamf.ASObject
     574
     575          return self.getClassAlias().klass
     576
     577 -    def getClassAlias(self):
     578          """
     579          Gets the class alias that is held by this definition.
     580
     581          @see: L{get_class_alias<pyamf.get_class_alias>}.
     582          @rtype: L{ClassAlias<pyamf.ClassAlias>}
     583          @return: Class alias.
     584          """
     585          if not isinstance(self.alias, pyamf.ClassAlias):
     586              self.alias = pyamf.get_class_alias(self.alias)
     587
     588          return self.alias
     589
     590 -    def getClass(self):
     591          """
     592          Gets the referenced class that is held by this definition.
     593          """
     594          if hasattr(self, '_klass'):
     595              return self._klass
     596
     597          self._klass = self._getClass()
     598
     599          return self._klass
     600
     601      klass = property(getClass)
     602
     603 -    def getStaticAttrs(self, obj, codec=None):
     604          """
     605          Returns a list of static attributes based on C{obj}. Once built,
     606          this list is immutable.
     607
     608          @param obj: The object to determine the static attributes from.
     609          @type obj: C{mixed}.
     610          @since: 0.4
     611          """
     612          if hasattr(self, 'static_attrs'):
     613              return self.static_attrs
     614
     615          if not self.alias:
     616              if hasattr(obj, '__slots__'):
     617                  return obj.__slots__
     618
     619              return []
     620
     621          static_attrs, dynamic_attrs = self.alias.getAttrs(obj, codec=self)
     622
     623          if static_attrs is None:
     624              self.static_attrs = []
     625          else:
     626              self.static_attrs = static_attrs
     627
     628          return self.static_attrs
     629
     630 -    def getAttrs(self, obj, codec=None):
     631          """
     632          Returns a C{tuple} containing a dict of static and dynamic attributes
     633          for C{obj}.
     634          """
     635          if self.alias is not None:
     636              return self.alias.getAttributes(obj, codec=codec)
     637
     638          dynamic_attrs = util.get_attrs(obj)
     639          static_attrs = {}
     640
     641          for a in self.getStaticAttrs(obj, codec=codec):
     642              static_attrs[a] = dynamic_attrs[a]
     643              del dynamic_attrs[a]
     644
     645          return static_attrs, dynamic_attrs
     646
     647 -    def createInstance(self, codec=None):
     648          """
     649          Creates a new instance.
     650          """
     651          if self.alias:
     652              obj = self.alias.createInstance(codec=codec)
     653          else:
     654              klass = self.getClass()
     655              obj = klass()
     656
     657          return obj
     658
     659 -    def applyAttributes(self, obj, attrs, codec=None):
     660          """
     661          Applies a collection of attributes C{attrs} to object C{obj}
     662          """
     663          if self.alias:
     664              self.alias.applyAttributes(obj, attrs, codec=codec)
     665          else:
     666              util.set_attrs(obj, attrs)
     667
     668 -class Context(pyamf.BaseContext):
     669      """
     670      I hold the AMF3 context for en/decoding streams.
     671
     672      @ivar strings: A list of string references.
     673      @type strings: C{list}
     674      @ivar classes: A list of L{ClassDefinition}.
     675      @type classes: C{list}
     676      @ivar legacy_xml: A list of legacy encoded XML documents.
     677      @type legacy_xml: C{list}
     678      """
     679
     680 -    def __init__(self):
     681          self.strings = util.IndexedCollection(use_hash=True)
     682          self.classes = util.IndexedCollection()
     683          self.class_defs = util.IndexedCollection()
     684          self.legacy_xml = util.IndexedCollection()
     685          self.object_aliases = util.IndexedMap() # Maps one object to another
     686
     687          pyamf.BaseContext.__init__(self)
     688
     689 -    def clear(self):
     690          """
     691          Clears the context.
     692          """
     693          pyamf.BaseContext.clear(self)
     694
     695          self.strings.clear()
     696          self.classes.clear()
     697          self.class_defs.clear()
     698          self.legacy_xml.clear()
     699          self.object_aliases.clear()
     700
     701 -    def reset(self):
     702          """
     703          Resets the context.
     704
     705          @see: L{pyamf.BaseContext.reset}
     706          """
     707          pyamf.BaseContext.reset(self)
     708
     709          self.strings.clear()
     710          self.classes.clear()
     711          self.class_defs.clear()
     712          self.legacy_xml.clear()
     713          self.object_aliases.clear()
     714
     715 -    def setObjectAlias(self, obj, alias):
     716          """
     717          Maps an object to an aliased object.
     718
     719          @since: 0.4
     720          """
     721          self.object_aliases.map(obj, alias)
     722
     723 -    def getObjectAlias(self, obj):
     724          """
     725          Get an alias of an object.
     726
     727          @since: 0.4
     728          """
     729          ref = self.object_aliases.getReferenceTo(obj)
     730          return self.object_aliases.getMappedByReference(ref)
     731
     732 -    def getString(self, ref):
     733          """
     734          Gets a string based on a reference C{ref}.
     735
     736          @param ref: The reference index.
     737          @type ref: C{str}
     738          @raise ReferenceError: The referenced string could not be found.
     739
     740          @rtype: C{str}
     741          @return: The referenced string.
     742          """
     743          try:
     744              return self.strings.getByReference(ref)
     745          except pyamf.ReferenceError:
     746              raise pyamf.ReferenceError("String reference %r not found" % (ref,))
     747
     748 -    def getStringReference(self, s):
     749          """
     750          Return string reference.
     751
     752          @type s: C{str}
     753          @param s: The referenced string.
     754          @raise ReferenceError: The string reference could not be found.
     755          @return: The reference index to the string.
     756          @rtype: C{int}
     757          """
     758          try:
     759              return self.strings.getReferenceTo(s)
     760          except ValueError:
     761              raise pyamf.ReferenceError("Reference for string %r not found" % (s,))
     762
     763 -    def addString(self, s):
     764          """
     765          Creates a reference to C{s}. If the reference already exists, that
     766          reference is returned.
     767
     768          @type s: C{str}
     769          @param s: The string to be referenced.
     770          @rtype: C{int}
     771          @return: The reference index.
     772
     773          @raise TypeError: The parameter C{s} is not of C{basestring} type.
     774          @raise ValueError: Trying to store a reference to an empty string.
     775          """
     776          if not isinstance(s, basestring):
     777              raise TypeError
     778
     779          if len(s) == 0:
     780              # do not store empty string references
     781              raise ValueError("Cannot store a reference to an empty string")
     782
     783          return self.strings.append(s)
     784
     785 -    def getClassDefinition(self, ref):
     786          """
     787          Return class reference.
     788
     789          @raise ReferenceError: The class reference could not be found.
     790          @return: Class reference.
     791          """
     792          try:
     793              return self.class_defs.getByReference(ref)
     794          except IndexError:
     795              raise pyamf.ReferenceError("Class reference %r not found" % (ref,))
     796
     797 -    def getClassDefinitionReference(self, class_def):
     798          """
     799          Return class definition reference.
     800
     801          @type class_def: L{ClassDefinition} or C{instance} or C{class}
     802          @param class_def: The class definition reference to be found.
     803          @raise ReferenceError: The reference could not be found.
     804          @raise TypeError: Unable to determine class.
     805          @return: The reference to C{class_def}.
     806          @rtype: C{int}
     807          """
     808          if isinstance(class_def, ClassDefinition):
     809              try:
     810                  return self.class_defs.getReferenceTo(class_def)
     811              except KeyError:
     812                  raise pyamf.ReferenceError("Reference for class %s not found" % (class_def.klass,))
     813
     814          if isinstance(class_def, (type, types.ClassType)):
     815              try:
     816                  return self.classes.getReferenceTo(class_def)
     817              except pyamf.ReferenceError:
     818                  raise pyamf.ReferenceError("Reference for class definition for %s not found" % (class_def,))
     819          elif isinstance(class_def, (types.InstanceType, types.ObjectType)):
     820              try:
     821                  return self.classes.getReferenceTo(class_def.__class__)
     822              except pyamf.ReferenceError:
     823                  raise pyamf.ReferenceError("Reference for class definition for %s not found" % (class_def.__class__,))
     824
     825          raise TypeError('Unable to determine class for %r' % (class_def,))
     826
     827 -    def addClassDefinition(self, class_def):
     828          """
     829          Creates a reference to C{class_def}.
     830
     831          @param class_def: C{ClassDefinition} instance.
     832          """
     833          try:
     834              return self.class_defs.getReferenceTo(class_def)
     835          except pyamf.ReferenceError:
     836              try:
     837                  self.classes.append(class_def.klass)
     838              except pyamf.UnknownClassAlias:
     839                  pass
     840
     841              return self.class_defs.append(class_def)
     842
     843 -    def removeClassDefinition(self, class_def):
     844          """
     845          Removes a C{ClassDefinition} reference from this context.
     846          """
     847          idx = self.rev_classes[id(class_def)]
     848
     849          del self.rev_classes[id(class_def)]
     850          del self.classes[idx]
     851
     852 -    def getLegacyXML(self, ref):
     853          """
     854          Return the legacy XML reference. This is the C{flash.xml.XMLDocument}
     855          class in ActionScript 3.0 and the top-level C{XML} class in
     856          ActionScript 1.0 and 2.0.
     857
     858          @type ref: C{int}
     859          @param ref: The reference index.
     860          @raise ReferenceError: The legacy XML reference could not be found.
     861          @return: Instance of L{ET<util.ET>}
     862          """
     863          try:
     864              return self.legacy_xml.getByReference(ref)
     865          except pyamf.ReferenceError:
     866              raise pyamf.ReferenceError("Legacy XML reference %r not found" % (ref,))
     867
     868 -    def getLegacyXMLReference(self, doc):
     869          """
     870          Return legacy XML reference.
     871
     872          @type doc: L{ET<util.ET>}
     873          @param doc: The XML document to reference.
     874          @raise ReferenceError: The reference could not be found.
     875          @return: The reference to C{doc}.
     876          @rtype: C{int}
     877          """
     878          try:
     879              return self.legacy_xml.getReferenceTo(doc)
     880          except pyamf.ReferenceError:
     881              raise pyamf.ReferenceError("Reference for document %r not found" % (doc,))
     882
     883 -    def addLegacyXML(self, doc):
     884          """
     885          Creates a reference to C{doc}.
     886
     887          If C{doc} is already referenced that index will be returned. Otherwise
     888          a new index will be created.
     889
     890          @type doc: L{ET<util.ET>}
     891          @param doc: The XML document to reference.
     892          @rtype: C{int}
     893          @return: The reference to C{doc}.
     894          """
     895          return self.legacy_xml.append(doc)
     896
     897 -    def __copy__(self):
     898          return self.__class__()
     899
     900 -class Decoder(pyamf.BaseDecoder):
     901      """
     902      Decodes an AMF3 data stream.
     903      """
     904      context_class = Context
     905
     906      type_map = {
     907          ASTypes.UNDEFINED:  'readUndefined',
     908          ASTypes.NULL:       'readNull',
     909          ASTypes.BOOL_FALSE: 'readBoolFalse',
     910          ASTypes.BOOL_TRUE:  'readBoolTrue',
     911          ASTypes.INTEGER:    'readSignedInteger',
     912          ASTypes.NUMBER:     'readNumber',
     913          ASTypes.STRING:     'readString',
     914          ASTypes.XML:        'readXML',
     915          ASTypes.DATE:       'readDate',
     916          ASTypes.ARRAY:      'readArray',
     917          ASTypes.OBJECT:     'readObject',
     918          ASTypes.XMLSTRING:  'readXMLString',
     919          ASTypes.BYTEARRAY:  'readByteArray',
     920      }
     921
     922 -    def __init__(self, data=None, context=None, strict=False, use_proxies=None):
     923          pyamf.BaseDecoder.__init__(self, data, context, strict)
     924
     925          if use_proxies is None:
     926              self.use_proxies = use_proxies_default
     927          else:
     928              self.use_proxies = use_proxies
     929
     930 -    def readType(self):
     931          """
     932          Read and returns the next byte in the stream and determine its type.
     933
     934          @raise DecodeError: AMF3 type not recognized.
     935          @return: AMF3 type.
     936          @rtype: C{int}
     937          """
     938          type = self.stream.read_uchar()
     939
     940          if type not in ACTIONSCRIPT_TYPES:
     941              raise pyamf.DecodeError("Unknown AMF3 type 0x%02x at %d" % (type, self.stream.tell() - 1))
     942
     943          return type
     944
     945 -    def readUndefined(self):
     946          """
     947          Read undefined.
     948          """
     949          return pyamf.Undefined
     950
     951 -    def readNull(self):
     952          """
     953          Read null.
     954
     955          @return: C{None}
     956          @rtype: C{None}
     957          """
     958          return None
     959
     960 -    def readBoolFalse(self):
     961          """
     962          Returns C{False}.
     963
     964          @return: C{False}
     965          @rtype: C{bool}
     966          """
     967          return False
     968
     969 -    def readBoolTrue(self):
     970          """
     971          Returns C{True}.
     972
     973          @return: C{True}
     974          @rtype: C{bool}
     975          """
     976          return True
     977
     978 -    def readNumber(self):
     979          """
     980          Read number.
     981          """
     982          return self.stream.read_double()
     983
     984 -    def readUnsignedInteger(self):
     985          """
     986          Reads and returns an unsigned integer from the stream.
     987          """
     988          return self.readInteger(False)
     989
     990 -    def readSignedInteger(self):
     991          """
     992          Reads and returns a signed integer from the stream.
     993          """
     994          return self.readInteger(True)
     995
     996 -    def readInteger(self, signed=False):
     997          """
     998          Reads and returns an integer from the stream.
     999
    1000          @type signed: C{bool}
    1001          @see: U{Parsing integers on OSFlash
    1002          <http://osflash.org/amf3/parsing_integers>} for the AMF3 integer data
    1003          format.
    1004          """
    1005          return decode_int(self.stream, signed)
    1006
    1007 -    def readString(self, use_references=True):
    1008          """
    1009          Reads and returns a string from the stream.
    1010
    1011          @type use_references: C{bool}
    1012          """
    1013          def readLength():
    1014              x = self.readUnsignedInteger()
    1015
    1016              return (x >> 1, x & REFERENCE_BIT == 0)
    1017
    1018          length, is_reference = readLength()
    1019
    1020          if use_references and is_reference:
    1021              return self.context.getString(length)
    1022
    1023          buf = self.stream.read(length)
    1024          result = unicode(buf, "utf8")
    1025
    1026          if len(result) != 0 and use_references:
    1027              self.context.addString(result)
    1028
    1029          return result
    1030
    1031 -    def readDate(self):
    1032          """
    1033          Read date from the stream.
    1034
    1035          The timezone is ignored as the date is always in UTC.
    1036          """
    1037          ref = self.readUnsignedInteger()
    1038
    1039          if ref & REFERENCE_BIT == 0:
    1040              return self.context.getObject(ref >> 1)
    1041
    1042          ms = self.stream.read_double()
    1043          result = util.get_datetime(ms / 1000.0)
    1044
    1045          self.context.addObject(result)
    1046
    1047          return result
    1048
    1049 -    def readArray(self):
    1050          """
    1051          Reads an array from the stream.
    1052
    1053          @warning: There is a very specific problem with AMF3 where the first
    1054          three bytes of an encoded empty C{dict} will mirror that of an encoded
    1055          C{{'': 1, '2': 2}}
    1056
    1057          @see: U{Docuverse blog (external)
    1058          <http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug>}
    1059          """
    1060          size = self.readUnsignedInteger()
    1061
    1062          if size & REFERENCE_BIT == 0:
    1063              return self.context.getObject(size >> 1)
    1064
    1065          size >>= 1
    1066
    1067          key = self.readString().encode('utf8')
    1068
    1069          if key == "":
    1070              # integer indexes only -> python list
    1071              result = []
    1072              self.context.addObject(result)
    1073
    1074              for i in xrange(size):
    1075                  result.append(self.readElement())
    1076
    1077          else:
    1078              result = pyamf.MixedArray()
    1079              self.context.addObject(result)
    1080
    1081              while key != "":
    1082                  el = self.readElement()
    1083
    1084                  try:
    1085                      result[str(key)] = el
    1086                  except UnicodeError:
    1087                      result[key] = el
    1088
    1089                  key = self.readString().encode('utf8')
    1090
    1091              for i in xrange(size):
    1092                  el = self.readElement()
    1093                  result[i] = el
    1094
    1095          return result
    1096
    1097 -    def _getClassDefinition(self, ref):
    1098          """
    1099          Reads class definition from the stream.
    1100          """
    1101          class_ref = ref & REFERENCE_BIT == 0
    1102
    1103          ref >>= 1
    1104
    1105          if class_ref:
    1106              class_def = self.context.getClassDefinition(ref)
    1107          else:
    1108              class_name = self.readString()
    1109
    1110              if class_name == '':
    1111                  class_alias = None
    1112              else:
    1113                  try:
    1114                      class_alias = pyamf.get_class_alias(class_name)
    1115                  except pyamf.UnknownClassAlias:
    1116                      if self.strict:
    1117                          raise
    1118
    1119                      class_alias = pyamf.TypedObjectClassAlias(pyamf.TypedObject, class_name)
    1120
    1121              class_def = ClassDefinition(class_alias, encoding=ref & 0x03)
    1122              self.context.addClassDefinition(class_def)
    1123
    1124          return class_ref, class_def, ref >> 2
    1125
    1126 -    def readObject(self, _use_proxies=None):
    1127          """
    1128          Reads an object from the stream.
    1129
    1130          @raise pyamf.EncodeError: Decoding an object in amf3 tagged as amf0
    1131              only is not allowed.
    1132          @raise pyamf.DecodeError: Unknown object encoding.
    1133          """
    1134          if _use_proxies is None:
    1135              _use_proxies = self.use_proxies
    1136
    1137          def readStatic(is_ref, class_def, obj, num_attrs):
    1138              if not is_ref:
    1139                  class_def.static_attrs = []
    1140
    1141                  for i in range(num_attrs):
    1142                      key = self.readString().encode('utf8')
    1143
    1144                      class_def.static_attrs.append(key)
    1145
    1146              for attr in class_def.static_attrs:
    1147                  obj[attr] = self.readElement()
    1148
    1149          def readDynamic(is_ref, class_def, obj):
    1150              attr = self.readString().encode('utf8')
    1151
    1152              while attr != '':
    1153                  obj[attr] = self.readElement()
    1154                  attr = self.readString().encode('utf8')
    1155
    1156          ref = self.readUnsignedInteger()
    1157
    1158          if ref & REFERENCE_BIT == 0:
    1159              obj = self.context.getObject(ref >> 1)
    1160
    1161              if _use_proxies is True:
    1162                  obj = self.readProxyObject(obj)
    1163
    1164              return obj
    1165
    1166          ref >>= 1
    1167
    1168          class_ref, class_def, num_attrs = self._getClassDefinition(ref)
    1169
    1170          if class_def.alias and 'amf0' in class_def.alias.metadata:
    1171              raise pyamf.EncodeError("Decoding an object in amf3 tagged as amf0 only is not allowed")
    1172
    1173          obj = class_def.createInstance(codec=self)
    1174          obj_attrs = dict()
    1175
    1176          self.context.addObject(obj)
    1177
    1178          if class_def.encoding in (ObjectEncoding.EXTERNAL, ObjectEncoding.PROXY):
    1179              obj.__readamf__(DataInput(self))
    1180          elif class_def.encoding == ObjectEncoding.DYNAMIC:
    1181              readStatic(class_ref, class_def, obj_attrs, num_attrs)
    1182              readDynamic(class_ref, class_def, obj_attrs)
    1183          elif class_def.encoding == ObjectEncoding.STATIC:
    1184              readStatic(class_ref, class_def, obj_attrs, num_attrs)
    1185          else:
    1186              raise pyamf.DecodeError("Unknown object encoding")
    1187
    1188          class_def.applyAttributes(obj, obj_attrs, codec=self)
    1189
    1190          if _use_proxies is True:
    1191              obj = self.readProxyObject(obj)
    1192
    1193          return obj
    1194
    1195 -    def readProxyObject(self, proxy):
    1196          """
    1197          Return the source object of a proxied object.
    1198
    1199          @since: 0.4
    1200          """
    1201          if isinstance(proxy, ArrayCollection):
    1202              return list(proxy)
    1203          elif isinstance(proxy, ObjectProxy):
    1204              return proxy._amf_object
    1205          else:
    1206              return proxy
    1207
    1208 -    def _readXML(self, legacy=False):
    1209          """
    1210          Reads an object from the stream.
    1211
    1212          @type legacy: C{bool}
    1213          @param legacy: The read XML is in 'legacy' format.
    1214          """
    1215          ref = self.readUnsignedInteger()
    1216
    1217          if ref & REFERENCE_BIT == 0:
    1218              return self.context.getObject(ref >> 1)
    1219
    1220          xmlstring = self.stream.read(ref >> 1)
    1221
    1222          x = util.ET.XML(xmlstring)
    1223          self.context.addObject(x)
    1224
    1225          if legacy is True:
    1226              self.context.addLegacyXML(x)
    1227
    1228          return x
    1229
    1230 -    def readXMLString(self):
    1231          """
    1232          Reads a string from the data stream and converts it into
    1233          an XML Tree.
    1234
    1235          @return: The XML Document.
    1236          @rtype: L{ET<util.ET>}
    1237          """
    1238          return self._readXML()
    1239
    1240 -    def readXML(self):
    1241          """
    1242          Read a legacy XML Document from the stream.
    1243
    1244          @return: The XML Document.
    1245          @rtype: L{ET<util.ET>}
    1246          """
    1247          return self._readXML(True)
    1248
    1249 -    def readByteArray(self):
    1250          """
    1251          Reads a string of data from the stream.
    1252
    1253          Detects if the L{ByteArray} was compressed using C{zlib}.
    1254
    1255          @see: L{ByteArray}
    1256          @note: This is not supported in ActionScript 1.0 and 2.0.
    1257          """
    1258          ref = self.readUnsignedInteger()
    1259
    1260          if ref & REFERENCE_BIT == 0:
    1261              return self.context.getObject(ref >> 1)
    1262
    1263          buffer = self.stream.read(ref >> 1)
    1264
    1265          try:
    1266              buffer = zlib.decompress(buffer)
    1267              compressed = True
    1268          except zlib.error:
    1269              compressed = False
    1270
    1271          obj = ByteArray(buffer, context=self.context)
    1272          obj.compressed = compressed
    1273
    1274          self.context.addObject(obj)
    1275
    1276          return obj
    1277
    1278 -class Encoder(pyamf.BaseEncoder):
    1279      """
    1280      Encodes an AMF3 data stream.
    1281      """
    1282      context_class = Context
    1283
    1284      type_map = [
    1285          ((types.BuiltinFunctionType, types.BuiltinMethodType,
    1286              types.FunctionType, types.GeneratorType, types.ModuleType,
    1287              types.LambdaType, types.MethodType), "writeFunc"),
    1288          ((bool,), "writeBoolean"),
    1289          ((types.NoneType,), "writeNull"),
    1290          ((int,long), "writeInteger"),
    1291          ((float,), "writeNumber"),
    1292          ((types.StringTypes,), "writeString"),
    1293          ((ByteArray,), "writeByteArray"),
    1294          ((datetime.date, datetime.datetime), "writeDate"),
    1295          ((util.is_ET_element,), "writeXML"),
    1296          ((lambda x: x is pyamf.Undefined,), "writeUndefined"),
    1297          ((types.InstanceType, types.ObjectType,), "writeInstance"),
    1298      ]
    1299
    1300 -    def __init__(self, data=None, context=None, strict=False, use_proxies=None):
    1301          pyamf.BaseEncoder.__init__(self, data, context, strict)
    1302
    1303          if use_proxies is None:
    1304              self.use_proxies = use_proxies_default
    1305          else:
    1306              self.use_proxies = use_proxies
    1307
    1308 -    def writeElement(self, data, use_references=True):
    1309          """
    1310          Writes the data.
    1311
    1312          @type   data: C{mixed}
    1313          @param  data: The data to be encoded to the AMF3 data stream.
    1314          @type   use_references: C{bool}
    1315          @param  use_references: Default is C{True}.
    1316          @raise EncodeError: Cannot find encoder func for C{data}.
    1317          """
    1318          func = self._writeElementFunc(data)
    1319
    1320          if func is None:
    1321              raise pyamf.EncodeError("Cannot find encoder func for %r" % (data,))
    1322          else:
    1323              try:
    1324                  if isinstance(func, pyamf.CustomTypeFunc):
    1325                      func(data)
    1326                  else:
    1327                      func(data, use_references=use_references)
    1328              except (KeyboardInterrupt, SystemExit):
    1329                  raise
    1330              except:
    1331                  raise
    1332
    1333 -    def writeType(self, type):
    1334          """
    1335          Writes the data type to the stream.
    1336
    1337          @param type: ActionScript type.
    1338          @raise EncodeError: AMF3 type is not recognized.
    1339          @see: L{ACTIONSCRIPT_TYPES}
    1340          """
    1341          if type not in ACTIONSCRIPT_TYPES:
    1342              raise pyamf.EncodeError("Unknown AMF3 type 0x%02x at %d" % (
    1343                  type, self.stream.tell() - 1))
    1344
    1345          self.stream.write_uchar(type)
    1346
    1347 -    def writeFunc(self, *args, **kwargs):
    1348          """
    1349          Functions cannot be serialised.
    1350          """
    1351          raise pyamf.EncodeError("Callables cannot be serialised")
    1352
    1353 -    def writeUndefined(self, d, use_references=True):
    1354          """
    1355          Writes an C{pyamf.Undefined} value to the stream.
    1356
    1357          @param d: The C{undefined} data to be encoded to the AMF3 data stream.
    1358          @type use_references: C{bool}
    1359          @param use_references: Default is C{True}.
    1360          """
    1361          self.writeType(ASTypes.UNDEFINED)
    1362
    1363 -    def writeNull(self, n, use_references=True):
    1364          """
    1365          Writes a C{null} value to the stream.
    1366
    1367          @param n: The C{null} data to be encoded to the AMF3 data stream.
    1368          @type n: C{null} data.
    1369          @type use_references: C{bool}
    1370          @param use_references: Default is C{True}.
    1371          """
    1372          self.writeType(ASTypes.NULL)
    1373
    1374 -    def writeBoolean(self, n, use_references=True):
    1375          """
    1376          Writes a Boolean to the stream.
    1377
    1378          @param n: The C{boolean} data to be encoded to the AMF3 data stream.
    1379          @type n: C{bool}
    1380          @type   use_references: C{bool}
    1381          @param  use_references: Default is C{True}.
    1382          """
    1383          if n:
    1384              self.writeType(ASTypes.BOOL_TRUE)
    1385          else:
    1386              self.writeType(ASTypes.BOOL_FALSE)
    1387
    1388 -    def _writeInteger(self, n):
    1389          """
    1390          AMF3 integers are encoded.
    1391
    1392          @param n: The integer data to be encoded to the AMF3 data stream.
    1393          @type n: integer data
    1394
    1395          @see: U{Parsing Integers on OSFlash
    1396          <http://osflash.org/documentation/amf3/parsing_integers>}
    1397          for more info.
    1398          """
    1399          self.stream.write(encode_int(n))
    1400
    1401 -    def writeInteger(self, n, use_references=True):
    1402          """
    1403          Writes an integer to the stream.
    1404
    1405          @type   n: integer data
    1406          @param  n: The integer data to be encoded to the AMF3 data stream.
    1407          @type   use_references: C{bool}
    1408          @param  use_references: Default is C{True}.
    1409          """
    1410          if n & 0xf0000000 not in [0, 0xf0000000]:
    1411              self.writeNumber(n)
    1412
    1413              return
    1414
    1415          self.writeType(ASTypes.INTEGER)
    1416          self.stream.write(encode_int(n))
    1417
    1418 -    def writeNumber(self, n, use_references=True):
    1419          """
    1420          Writes a non integer to the stream.
    1421
    1422          @type   n: number data
    1423          @param  n: The number data to be encoded to the AMF3 data stream.
    1424          @type   use_references: C{bool}
    1425          @param  use_references: Default is C{True}
    1426          """
    1427          self.writeType(ASTypes.NUMBER)
    1428          self.stream.write_double(n)
    1429
    1430 -    def _writeString(self, n, use_references=True):
    1431          """
    1432          Writes a raw string to the stream.
    1433
    1434          @type   n: C{str} or C{unicode}
    1435          @param  n: The string data to be encoded to the AMF3 data stream.
    1436          @type   use_references: C{bool}
    1437          @param  use_references: Default is C{True}.
    1438          """
    1439          if not isinstance(n, basestring):
    1440              bytes = unicode(n).encode('utf8')
    1441              n = bytes
    1442          elif isinstance(n, unicode):
    1443              bytes = n.encode('utf8')
    1444          else:
    1445              bytes = n
    1446
    1447          if len(bytes) == 0:
    1448              self._writeInteger(REFERENCE_BIT)
    1449
    1450              return
    1451
    1452          if use_references:
    1453              try:
    1454                  ref = self.context.getStringReference(n)
    1455                  self._writeInteger(ref << 1)
    1456
    1457                  return
    1458              except pyamf.ReferenceError:
    1459                  self.context.addString(n)
    1460
    1461          self._writeInteger((len(bytes) << 1) | REFERENCE_BIT)
    1462          self.stream.write(bytes)
    1463
    1464 -    def writeString(self, n, use_references=True):
    1465          """
    1466          Writes a string to the stream. If C{n} is not a unicode string, an
    1467          attempt will be made to convert it.
    1468
    1469          @type   n: C{basestring}
    1470          @param  n: The string data to be encoded to the AMF3 data stream.
    1471          @type   use_references: C{bool}
    1472          @param  use_references: Default is C{True}.
    1473          """
    1474          self.writeType(ASTypes.STRING)
    1475          self._writeString(n, use_references)
    1476
    1477 -    def writeDate(self, n, use_references=True):
    1478          """
    1479          Writes a C{datetime} instance to the stream.
    1480
    1481          @type n: L{datetime}
    1482          @param n: The C{Date} data to be encoded to the AMF3 data stream.
    1483          @type   use_references: C{bool}
    1484          @param  use_references: Default is C{True}.
    1485          """
    1486          self.writeType(ASTypes.DATE)
    1487
    1488          if use_references is True:
    1489              try:
    1490                  ref = self.context.getObjectReference(n)
    1491                  self._writeInteger(ref << 1)
    1492
    1493                  return
    1494              except pyamf.ReferenceError:
    1495                  self.context.addObject(n)
    1496
    1497          self._writeInteger(REFERENCE_BIT)
    1498
    1499          ms = util.get_timestamp(n)
    1500          self.stream.write_double(ms * 1000.0)
    1501
    1502 -    def writeList(self, n, use_references=True, _use_proxies=None):
    1503          """
    1504          Writes a C{tuple}, C{set} or C{list} to the stream.
    1505
    1506          @type n: One of C{__builtin__.tuple}, C{__builtin__.set}
    1507              or C{__builtin__.list}
    1508          @param n: The C{list} data to be encoded to the AMF3 data stream.
    1509          @type use_references: C{bool}
    1510          @param use_references: Default is C{True}.
    1511          """
    1512          # Encode lists as ArrayCollections
    1513          if _use_proxies is None:
    1514              _use_proxies = self.use_proxies
    1515
    1516          if _use_proxies is True:
    1517              try:
    1518                  ref_obj = self.context.getObjectAlias(n)
    1519              except pyamf.ReferenceError:
    1520                  proxy = ArrayCollection(n)
    1521                  self.context.setObjectAlias(n, proxy)
    1522                  ref_obj = proxy
    1523
    1524              self.writeObject(ref_obj, use_references)
    1525
    1526              return
    1527
    1528          self.writeType(ASTypes.ARRAY)
    1529
    1530          if use_references is True:
    1531              try:
    1532                  ref = self.context.getObjectReference(n)
    1533                  self._writeInteger(ref << 1)
    1534
    1535                  return
    1536              except pyamf.ReferenceError:
    1537                  self.context.addObject(n)
    1538
    1539          self._writeInteger(len(n) << 1 | REFERENCE_BIT)
    1540
    1541          self.stream.write_uchar(0x01)
    1542
    1543          for x in n:
    1544              self.writeElement(x)
    1545
    1546 -    def writeDict(self, n, use_references=True, _use_proxies=None):
    1547          """
    1548          Writes a C{dict} to the stream.
    1549
    1550          @type n: C{__builtin__.dict}
    1551          @param n: The C{dict} data to be encoded to the AMF3 data stream.
    1552          @type use_references: C{bool}
    1553          @param use_references: Default is C{True}.
    1554          @raise ValueError: Non C{int}/C{str} key value found in the C{dict}
    1555          @raise EncodeError: C{dict} contains empty string keys.
    1556          """
    1557
    1558          # Design bug in AMF3 that cannot read/write empty key strings
    1559          # http://www.docuverse.com/blog/donpark/2007/05/14/flash-9-amf3-bug
    1560          # for more info
    1561          if n.has_key(''):
    1562              raise pyamf.EncodeError("dicts cannot contain empty string keys")
    1563
    1564          if _use_proxies is None:
    1565              _use_proxies = self.use_proxies
    1566
    1567          if _use_proxies is True:
    1568              try:
    1569                  ref_obj = self.context.getObjectAlias(n)
    1570              except pyamf.ReferenceError:
    1571                  dictObj = pyamf.ASObject(n)
    1572                  proxy = ObjectProxy(dictObj)
    1573                  self.context.setObjectAlias(n, proxy)
    1574                  ref_obj = proxy
    1575
    1576              self.writeObject(ref_obj, use_references)
    1577
    1578              return
    1579
    1580          self.writeType(ASTypes.ARRAY)
    1581
    1582          if use_references:
    1583              try:
    1584                  ref = self.context.getObjectReference(n)
    1585                  self._writeInteger(ref << 1)
    1586
    1587                  return
    1588              except pyamf.ReferenceError:
    1589                  self.context.addObject(n)
    1590
    1591          # The AMF3 spec demands that all str based indicies be listed first
    1592          keys = n.keys()
    1593          int_keys = []
    1594          str_keys = []
    1595
    1596          for x in keys:
    1597              if isinstance(x, (int, long)):
    1598                  int_keys.append(x)
    1599              elif isinstance(x, (str, unicode)):
    1600                  str_keys.append(x)
    1601              else:
    1602                  raise ValueError("Non int/str key value found in dict")
    1603
    1604          # Make sure the integer keys are within range
    1605          l = len(int_keys)
    1606
    1607          for x in int_keys:
    1608              if l < x <= 0:
    1609                  # treat as a string key
    1610                  str_keys.append(x)
    1611                  del int_keys[int_keys.index(x)]
    1612
    1613          int_keys.sort()
    1614
    1615          # If integer keys don't start at 0, they will be treated as strings
    1616          if len(int_keys) > 0 and int_keys[0] != 0:
    1617              for x in int_keys:
    1618                  str_keys.append(str(x))
    1619                  del int_keys[int_keys.index(x)]
    1620
    1621          self._writeInteger(len(int_keys) << 1 | REFERENCE_BIT)
    1622
    1623          for x in str_keys:
    1624              self._writeString(x)
    1625              self.writeElement(n[x])
    1626
    1627          self.stream.write_uchar(0x01)
    1628
    1629          for k in int_keys:
    1630              self.writeElement(n[k])
    1631
    1632 -    def _getClassDefinition(self, obj):
    1633          """
    1634          Builds a class definition based on the C{obj}.
    1635          """
    1636          encoding = ObjectEncoding.DYNAMIC
    1637
    1638          alias = self.context.getClassAlias(obj.__class__)
    1639
    1640          if alias:
    1641              if 'dynamic' in alias.metadata:
    1642                  encoding = ObjectEncoding.DYNAMIC
    1643              elif 'static' in alias.metadata:
    1644                  encoding = ObjectEncoding.STATIC
    1645              elif 'external' in alias.metadata:
    1646                  encoding = ObjectEncoding.EXTERNAL
    1647
    1648          class_def = ClassDefinition(alias, encoding)
    1649
    1650          return class_def
    1651
    1652 -    def writeInstance(self, obj, use_references=True):
    1653          """
    1654          Read class definition.
    1655
    1656          @param obj: The class instance data to be encoded to the AMF3
    1657              data stream.
    1658          @type obj: instance data
    1659          @type use_references: C{bool}
    1660          @param use_references: Default is C{True}.
    1661          """
    1662          if obj.__class__ in (pyamf.MixedArray,):
    1663              self.writeDict(obj, use_references)
    1664          elif obj.__class__ in (list, set, tuple):
    1665              self.writeList(obj, use_references)
    1666          else:
    1667              self.writeObject(obj, use_references)
    1668
    1669 -    def writeObject(self, obj, use_references=True, _use_proxies=None):
    1670          """
    1671          Writes an object to the stream.
    1672
    1673          @param obj: The object data to be encoded to the AMF3 data stream.
    1674          @type obj: object data
    1675          @param use_references: Default is C{True}.
    1676          @type use_references: C{bool}
    1677          @raise EncodeError: Encoding an object in amf3 tagged as amf0 only.
    1678          """
    1679          if _use_proxies is None:
    1680              _use_proxies = self.use_proxies
    1681
    1682          if _use_proxies is True and obj.__class__ is dict:
    1683              try:
    1684                  ref_obj = self.context.getObjectAlias(obj)
    1685              except pyamf.ReferenceError:
    1686                  dictObj = pyamf.ASObject(obj)
    1687                  proxy = ObjectProxy(dictObj)
    1688
    1689                  self.context.setObjectAlias(obj, proxy)
    1690                  ref_obj = proxy
    1691
    1692              self.writeObject(ref_obj, use_references)
    1693
    1694              return
    1695
    1696          self.writeType(ASTypes.OBJECT)
    1697
    1698          if use_references is True:
    1699              try:
    1700                  ref = self.context.getObjectReference(obj)
    1701                  self._writeInteger(ref << 1)
    1702
    1703                  return
    1704              except pyamf.ReferenceError:
    1705                  self.context.addObject(obj)
    1706
    1707          try:
    1708              ref = self.context.getClassDefinitionReference(obj)
    1709              class_ref = True
    1710
    1711              self._writeInteger(ref << 2 | REFERENCE_BIT)
    1712          except pyamf.ReferenceError:
    1713              class_def = self._getClassDefinition(obj)
    1714
    1715              if class_def.alias and 'amf0' in class_def.alias.metadata:
    1716                  raise pyamf.EncodeError("Encoding an object in amf3 tagged as amf0 only")
    1717
    1718              self.context.addClassDefinition(class_def)
    1719
    1720              class_ref = False
    1721              ref = 0
    1722
    1723              if class_def.encoding != ObjectEncoding.EXTERNAL:
    1724                  ref += len(class_def.getStaticAttrs(obj)) << 4
    1725
    1726              self._writeInteger(ref | class_def.encoding << 2 | REFERENCE_BIT << 1 | REFERENCE_BIT)
    1727              self._writeString(class_def.name)
    1728          else:
    1729              class_def = self._getClassDefinition(obj)
    1730
    1731          if class_def.encoding in (ObjectEncoding.EXTERNAL, ObjectEncoding.PROXY):
    1732              obj.__writeamf__(DataOutput(self))
    1733          else:
    1734              static_attrs, dynamic_attrs = class_def.getAttrs(obj, codec=self)
    1735
    1736              if static_attrs is not None:
    1737                  attrs = class_def.getStaticAttrs(obj)
    1738
    1739                  if not class_ref:
    1740                      [self._writeString(attr) for attr in attrs]
    1741
    1742                  [self.writeElement(static_attrs[attr]) for attr in attrs]
    1743
    1744              if class_def.encoding is ObjectEncoding.DYNAMIC and dynamic_attrs is not None:
    1745                  for attr, value in dynamic_attrs.iteritems():
    1746                      self._writeString(attr)
    1747                      self.writeElement(value)
    1748
    1749                  self._writeString('')
    1750
    1751 -    def writeByteArray(self, n, use_references=True):
    1752          """
    1753          Writes a L{ByteArray} to the data stream.
    1754
    1755          @type   n: L{ByteArray}
    1756          @param  n: The L{ByteArray} data to be encoded to the AMF3 data stream.
    1757          @type   use_references: C{bool}
    1758          @param  use_references: Default is C{True}.
    1759          """
    1760          self.writeType(ASTypes.BYTEARRAY)
    1761
    1762          if use_references:
    1763              try:
    1764                  ref = self.context.getObjectReference(n)
    1765                  self._writeInteger(ref << 1)
    1766
    1767                  return
    1768              except pyamf.ReferenceError:
    1769                  self.context.addObject(n)
    1770
    1771          buf = str(n)
    1772          l = len(buf)
    1773          self._writeInteger(l << 1 | REFERENCE_BIT)
    1774          self.stream.write(buf)
    1775
    1776 -    def writeXML(self, n, use_references=True):
    1777          """
    1778          Writes a XML string to the data stream.
    1779
    1780          @type   n: L{ET<util.ET>}
    1781          @param  n: The XML Document to be encoded to the AMF3 data stream.
    1782          @type   use_references: C{bool}
    1783          @param  use_references: Default is C{True}.
    1784          """
    1785          try:
    1786              self.context.getLegacyXMLReference(n)
    1787              is_legacy = True
    1788          except pyamf.ReferenceError:
    1789              is_legacy = False
    1790
    1791          if is_legacy is True:
    1792              self.writeType(ASTypes.XML)
    1793          else:
    1794              self.writeType(ASTypes.XMLSTRING)
    1795
    1796          if use_references:
    1797              try:
    1798                  ref = self.context.getObjectReference(n)
    1799                  self._writeInteger(ref << 1)
    1800
    1801                  return
    1802              except pyamf.ReferenceError:
    1803                  self.context.addObject(n)
    1804
    1805          self._writeString(util.ET.tostring(n, 'utf-8'), False)
    1806
    1807 -def decode(stream, context=None, strict=False):
    1808      """
    1809      A helper function to decode an AMF3 datastream.
    1810
    1811      @type   stream: L{BufferedByteStream<util.BufferedByteStream>}
    1812      @param  stream: AMF3 data.
    1813      @type   context: L{Context}
    1814      @param  context: Context.
    1815      """
    1816      decoder = Decoder(stream, context, strict)
    1817
    1818      while 1:
    1819          try:
    1820              yield decoder.readElement()
    1821          except pyamf.EOStream:
    1822              break
    1823
    1824 -def encode(*args, **kwargs):
    1825      """
    1826      A helper function to encode an element into AMF3 format.
    1827
    1828      @type args: List of args to encode.
    1829      @keyword context: Any initial context to use.
    1830      @type context: L{Context}
    1831      @return: C{StringIO} type object containing the encoded AMF3 data.
    1832      @rtype: L{BufferedByteStream<pyamf.util.BufferedByteStream>}
    1833      """
    1834      context = kwargs.get('context', None)
    1835      buf = util.BufferedByteStream()
    1836      encoder = Encoder(buf, context)
    1837
    1838      for element in args:
    1839          encoder.writeElement(element)
    1840
    1841      return buf
    1842
    1843 -def _encode_int(n):
    1844      """
    1845      @raise OverflowError: Out of range.
    1846      """
    1847      if n & 0xf0000000 not in [0, 0xf0000000]:
    1848          raise OverflowError("Out of range")
    1849
    1850      bytes = ''
    1851      real_value = None
    1852
    1853      if n < 0:
    1854          n += 0x20000000
    1855
    1856      if n > 0x1fffff:
    1857          real_value = n
    1858          n >>= 1
    1859          bytes += chr(0x80 | ((n >> 21) & 0xff))
    1860
    1861      if n > 0x3fff:
    1862          bytes += chr(0x80 | ((n >> 14) & 0xff))
    1863
    1864      if n > 0x7f:
    1865          bytes += chr(0x80 | ((n >> 7) & 0xff))
    1866
    1867      if real_value is not None:
    1868          n = real_value
    1869
    1870      if n > 0x1fffff:
    1871          bytes += chr(n & 0xff)
    1872      else:
    1873          bytes += chr(n & 0x7f)
    1874
    1875      return bytes
    1876
    1877 -def _decode_int(stream, signed=False):
    1878      n = result = 0
    1879      b = stream.read_uchar()
    1880
    1881      while b & 0x80 != 0 and n < 3:
    1882          result <<= 7
    1883          result |= b & 0x7f
    1884          b = stream.read_uchar()
    1885          n += 1
    1886
    1887      if n < 3:
    1888          result <<= 7
    1889          result |= b
    1890      else:
    1891          result <<= 8
    1892          result |= b
    1893
    1894          if result & 0x10000000 != 0:
    1895              if signed:
    1896                  result -= 0x20000000
    1897              else:
    1898                  result <<= 1
    1899                  result += 1
    1900
    1901      return result
    1902
    1903  try:
    1904      from cpyamf.amf3 import encode_int
    1905  except ImportError:
    1906      encode_int = _encode_int
    1907  try:
    1908      from cpyamf.amf3 import decode_int
    1909  except ImportError:
    1910      decode_int = _decode_int
    1911