class ExpiringDictionary(dict):
    """An extension to standard Python dictionary objects which implements item
    expiration.

    Each item in ExpiringDictionary has its expiration time assigned, after
    which the item is removed from the mapping.

    :Ivariables:
        - `_timeouts`: a dictionary with timeout values and timeout callback for
          stored objects.
        - `_default_timeout`: the default timeout value (in seconds from now).
        - `_lock`: access synchronization lock.
    :Types:
        - `_timeouts`: `dict`
        - `_default_timeout`: `float`
        - `_lock`: :std:`threading.RLock`"""
    __slots__ = ['_timeouts', '_default_timeout', '_lock']
    def __init__(self, default_timeout = 300.0):
        """Initialize an `ExpiringDictionary` object.

        :Parameters:
            - `default_timeout`: default timeout value (in seconds) for stored
              objects.
        :Types:
            - `default_timeout`: `float`
        """
        dict.__init__(self)
        self._timeouts = {}
        self._default_timeout = default_timeout
        self._lock = threading.RLock()

    def __delitem__(self, key):
        with self._lock:
            logger.debug("expdict.__delitem__({0!r})".format(key))
            del self._timeouts[key]
            return dict.__delitem__(self, key)

    def __getitem__(self, key):
        with self._lock:
            logger.debug("expdict.__getitem__({0!r})".format(key))
            self._expire_item(key)
            return dict.__getitem__(self, key)

    def pop(self, key, default = _NO_DEFAULT):
        with self._lock:
            self._expire_item(key)
            del self._timeouts[key]
            if default is not _NO_DEFAULT:
                return dict.pop(self, key, default)
            else:
                return dict.pop(self, key)

    def __setitem__(self, key, value):
        logger.debug("expdict.__setitem__({0!r}, {1!r})".format(key, value))
        return self.set_item(key, value)

    def set_item(self, key, value, timeout = None, timeout_callback = None):
        """Set item of the dictionary.

        :Parameters:
            - `key`: the key.
            - `value`: the object to store.
            - `timeout`: timeout value for the object (in seconds from now).
            - `timeout_callback`: function to be called when the item expires.
              The callback should accept none, one (the key) or two (the key
              and the value) arguments.
        :Types:
            - `key`: any hashable value
            - `value`: any python object
            - `timeout`: `int`
            - `timeout_callback`: callable
        """
        with self._lock:
            logger.debug("expdict.__setitem__({0!r}, {1!r}, {2!r}, {3!r})"
                            .format(key, value, timeout, timeout_callback))
            if not timeout:
                timeout = self._default_timeout
            self._timeouts[key] = (time.time() + timeout, timeout_callback)
            return dict.__setitem__(self, key, value)

    def expire(self):
        """Do the expiration of dictionary items.

        Remove items that expired by now from the dictionary.

        :Return: time, in seconds, when the next item expires or `None`
        :returntype: `float`
        """
        with self._lock:
            logger.debug("expdict.expire. timeouts: {0!r}"
                                                    .format(self._timeouts))
            next_timeout = None
            for k in self._timeouts.keys():
                ret = self._expire_item(k)
                if ret is not None:
                    if next_timeout is None:
                        next_timeout = ret
                    else:
                        next_timeout = min(next_timeout, ret)
            return next_timeout

    def clear(self):
        with self._lock:
            self._timeouts.clear()
            dict.clear(self)

    def _expire_item(self, key):
        """Do the expiration of a dictionary item.

        Remove the item if it has expired by now.

        :Parameters:
            - `key`: key to the object.
        :Types:
            - `key`: any hashable value
        """
        (timeout, callback) = self._timeouts[key]
        now = time.time()
        if timeout <= now:
            item = dict.pop(self, key)
            del self._timeouts[key]
            if callback:
                try:
                    callback(key, item)
                except TypeError:
                    try:
                        callback(key)
                    except TypeError:
                        callback()
            return None
        else:
            return timeout - now class ProtocolError(Error):
    """Raised when there is something wrong with a stanza processed.

    When not processed earlier by an application, the exception will be catched
    by the stanza dispatcher to return XMPP error to the stanza sender, when
    allowed.

    ProtocolErrors handled internally by PyXMPP will be logged via the logging
    interface. Errors reported to the sender will be logged using
    "pyxmpp2.ProtocolError.reported" channel and the ignored errors using
    "pyxmpp2.ProtocolError.ignored" channel. Both with the "debug" level.

    """

    logger_reported = logging.getLogger("pyxmpp2.ProtocolError.reported")
    logger_ignored = logging.getLogger("pyxmpp2.ProtocolError.ignored")

    def __init__(self, xmpp_name, message):
        Error.__init__(self, xmpp_name, message)

    @property
    def xmpp_name(self):
        """XMPP error name which should be reported"""
        return self.args[0]
    @property
    def message(self):
        """The error message."""
        return self.args[1]
    def log_reported(self):
        """Log message via the "pyxmpp.ProtocolError.reported" logger."""
        self.logger_reported.debug(u"Protocol error detected: {0}"
                                                        .format(self.message))
    def log_ignored(self):
        """Log message via the "pyxmpp.ProtocolError.ignored" logger."""
        self.logger_ignored.debug(u"Protocol error detected: {0}"
                                                        .format(self.message))
    def __unicode__(self):
        return str(self.args[1])
    def __repr__(self):
        return "<ProtocolError %r %r>" % (self.xmpp_name, self.message) class BadRequestProtocolError(ProtocolError):
    """Raised when invalid stanza is processed and 'bad-request' error should
    be reported."""
    def __init__(self, message):
        ProtocolError.__init__(self, "bad-request", message) class ServiceUnavailableProtocolError(ProtocolError):
    """Raised when stanza requests a feature which is not (yet) implemented."""
    def __init__(self, message):
        ProtocolError.__init__(self, "service-unavailable", message) class NoRouteError(Error):
    """Raised when a stanza cannot be routed internally."""
    pass class Stanza(object):
    """Base class for all XMPP stanzas.

    :Ivariables:
        - `_payload`: the stanza payload
        - `_error`: error associated a stanza of type "error"
        - `_namespace`: namespace of this stanza element
        - `_return_path`: weak reference to the return route object
    :Types:
        - `_payload`: `list` of (`unicode`, `StanzaPayload`)
        - `_error`: `pyxmpp2.error.StanzaErrorElement`
        - `_namespace`: `unicode`
        - `_return_path`: weakref to `StanzaRoute`
    """
    # pylint: disable-msg=R0902
    element_name = "Unknown"
    def __init__(self, element, from_jid = None, to_jid = None,
                            stanza_type = None, stanza_id = None,
                            error = None, error_cond = None,
                            return_path = None, language = None):
        """Initialize a Stanza object.

        :Parameters:
            - `element`: XML element of this stanza, or element name for a new
              stanza. If element is given it must not be modified later,
              unless `decode_payload()` and `mark_dirty()` methods are called
              first (the element changes won't affec the stanza then).
            - `from_jid`: sender JID.
            - `to_jid`: recipient JID.
            - `stanza_type`: staza type: one of: "get", "set", "result" or
              "error".
            - `stanza_id`: stanza id -- value of stanza's "id" attribute. If
              not given, then unique for the session value is generated.
            - `error`: error object. Ignored if `stanza_type` is not "error".
            - `error_cond`: error condition name. Ignored if `stanza_type` is
              not "error" or `error` is not None.
            - `return_path`: route for sending responses to this stanza. Will
              be weakly referenced.
            - `language`: default language for the stanza content
        :Types:
            - `element`: `unicode` or :etree:`ElementTree.Element`
            - `from_jid`: `JID`
            - `to_jid`: `JID`
            - `stanza_type`: `unicode`
            - `stanza_id`: `unicode`
            - `error`: `pyxmpp.error.StanzaErrorElement`
            - `error_cond`: `unicode`
            - `return_path`: `StanzaRoute`
            - `language`: `unicode`
        """
        # pylint: disable-msg=R0913
        self._error = None
        self._from_jid = None
        self._to_jid = None
        self._stanza_type = None
        self._stanza_id = None
        self._language = language
        if isinstance(element, ElementClass):
            self._element = element
            self._dirty = False
            self._decode_attributes()
            if not element.tag.startswith("{"):
                raise ValueError("Element has no namespace")
            else:
                self._namespace, self.element_name = element.tag[1:].split("}")
                if self._namespace not in STANZA_NAMESPACES:
                    raise BadRequestProtocolError("Wrong stanza namespace")
            self._payload = None
        else:
            self._element = None
            self._dirty = True
            self.element_name = unicode(element)
            self._namespace = STANZA_CLIENT_NS
            self._payload = []

        self._ns_prefix = "{{{0}}}".format(self._namespace)
        self._element_qname = self._ns_prefix + self.element_name

        if from_jid is not None:
            self.from_jid = from_jid

        if to_jid is not None:
            self.to_jid = to_jid

        if stanza_type:
            self.stanza_type = stanza_type

        if stanza_id:
            self.stanza_id = stanza_id

        if self.stanza_type == "error":
            if error:
                self._error = StanzaErrorElement(error)
            elif error_cond:
                self._error = StanzaErrorElement(error_cond)
            else:
                self._decode_error()

        if return_path is not None:
            self._return_path = weakref.ref(return_path)

    def _decode_attributes(self):
        """Decode attributes of the stanza XML element
        and put them into the stanza properties."""
        try:
            from_jid = self._element.get('from')
            if from_jid:
                self._from_jid = JID(from_jid)
            to_jid = self._element.get('to')
            if to_jid:
                self._to_jid = JID(to_jid)
        except ValueError:
            raise JIDMalformedProtocolError
        self._stanza_type = self._element.get('type')
        self._stanza_id = self._element.get('id')
        lang = self._element.get(XML_LANG_QNAME)
        if lang:
            self._language = lang

    def _decode_error(self):
        """Decode error element of the stanza."""
        error_qname = self._ns_prefix + "error"
        for child in self._element:
            if child.tag == error_qname:
                self._error = StanzaErrorElement(child)
                return
        raise BadRequestProtocolError("Error element missing in"
                                                            " an error stanza")

    def copy(self):
        """Create a deep copy of the stanza.

        :returntype: `Stanza`"""
        result = Stanza(self.element_name, self.from_jid, self.to_jid,
                        self.stanza_type, self.stanza_id, self.error,
                        self._return_path())
        if self._payload is None:
            self.decode_payload()
        for payload in self._payload:
            result.add_payload(payload.copy())
        return result

    def serialize(self):
        """Serialize the stanza into a Unicode XML string.

        :return: serialized stanza.
        :returntype: `unicode`"""
        return serialize(self.get_xml())

    def as_xml(self):
        """Return the XML stanza representation.

        Always return an independent copy of the stanza XML representation,
        which can be freely modified without affecting the stanza.

        :returntype: :etree:`ElementTree.Element`"""
        attrs = {}
        if self._from_jid:
            attrs['from'] = unicode(self._from_jid)
        if self._to_jid:
            attrs['to'] = unicode(self._to_jid)
        if self._stanza_type:
            attrs['type'] = self._stanza_type
        if self._stanza_id:
            attrs['id'] = self._stanza_id
        if self._language:
            attrs[XML_LANG_QNAME] = self._language
        element = ElementTree.Element(self._element_qname, attrs)
        if self._payload is None:
            self.decode_payload()
        for payload in self._payload:
            element.append(payload.as_xml())
        if self._error:
            element.append(self._error.as_xml(
                                        stanza_namespace = self._namespace))
        return element

    def get_xml(self):
        """Return the XML stanza representation.

        This returns the original or cached XML representation, which
        may be much more efficient than `as_xml`.

        Result of this function should never be modified.

        :returntype: :etree:`ElementTree.Element`"""
        if not self._dirty:
            return self._element
        element = self.as_xml()
        self._element = element
        self._dirty = False
        return element

    def decode_payload(self, specialize = False):
        """Decode payload from the element passed to the stanza constructor.

        Iterates over stanza children and creates StanzaPayload objects for
        them. Called automatically by `get_payload()` and other methods that
        access the payload.

        For the `Stanza` class stanza namespace child elements will also be
        included as the payload. For subclasses these are no considered
        payload."""
        if self._payload is not None:
            # already decoded
            return
        if self._element is None:
            raise ValueError("This stanza has no element to decode""")
        payload = []
        if specialize:
            factory = payload_factory
        else:
            factory = XMLPayload
        for child in self._element:
            if self.__class__ is not Stanza:
                if child.tag.startswith(self._ns_prefix):
                    continue
            payload.append(factory(child))
        self._payload = payload

    @property
    def from_jid(self): # pylint: disable-msg=E0202
        """Source JID of the stanza.

        :returntype: `JID`
        """
        return self._from_jid

    @from_jid.setter # pylint: disable-msg=E1101
    def from_jid(self, from_jid): # pylint: disable-msg=E0202,E0102,C0111
        if from_jid is None:
            self._from_jid = None
        else:
            self._from_jid = JID(from_jid)
        self._dirty = True

    @property
    def to_jid(self): # pylint: disable-msg=E0202
        """Destination JID of the stanza.

        :returntype: `JID`
        """
        return self._to_jid

    @to_jid.setter # pylint: disable-msg=E1101
    def to_jid(self, to_jid): # pylint: disable-msg=E0202,E0102,C0111
        if to_jid is None:
            self._to_jid = None
        else:
            self._to_jid = JID(to_jid)
        self._dirty = True

    @property
    def stanza_type(self): # pylint: disable-msg=C0111,E0202
        """Stanza type, one of: "get", "set", "result" or "error".

        :returntype: `unicode`
        """
        return self._stanza_type

    @stanza_type.setter # pylint: disable-msg=E1101
    def stanza_type(self, stanza_type): # pylint: disable-msg=E0202,E0102,C0111
        self._stanza_type = unicode(stanza_type)
        self._dirty = True

    @property
    def stanza_id(self): # pylint: disable-msg=C0111,E0202
        """Stanza id.

        :returntype: `unicode`
        """
        return self._stanza_id

    @stanza_id.setter # pylint: disable-msg=E1101
    def stanza_id(self, stanza_id): # pylint: disable-msg=E0202,E0102,C0111
        self._stanza_id = unicode(stanza_id)
        self._dirty = True

    @property
    def error(self): # pylint: disable-msg=E0202
        """Stanza error element.

        :returntype: `StanzaErrorElement`
        """
        return self._error

    @error.setter # pylint: disable-msg=E1101
    def error(self, error): # pylint: disable-msg=E0202,E0102,C0111
        self._error = error
        self._dirty = True

    @property
    def return_path(self): # pylint: disable-msg=E0202
        """Stream the stanza was received from.

        :returntype: `StanzaRoute`
        """
        return self._return_path()

    def mark_dirty(self):
        """Mark the stanza 'dirty' so the XML representation will be
        re-built the next time it is requested.

        This should be called each time the payload attached to the stanza is
        modifed."""
        self._dirty = True

    def set_payload(self, payload):
        """Set stanza payload to a single item.

        All current stanza content of will be dropped.
        Marks the stanza dirty.

        :Parameters:
            - `payload`: XML element or stanza payload object to use
        :Types:
            - `payload`: :etree:`ElementTree.Element` or `StanzaPayload`
        """
        if isinstance(payload, ElementClass):
            self._payload = [ XMLPayload(payload) ]
        elif isinstance(payload, StanzaPayload):
            self._payload = [ payload ]
        else:
            raise TypeError("Bad payload type")
        self._dirty = True

    def add_payload(self, payload):
        """Add new the stanza payload.

        Marks the stanza dirty.

        :Parameters:
            - `payload`: XML element or stanza payload object to add
        :Types:
            - `payload`: :etree:`ElementTree.Element` or `StanzaPayload`
        """
        if self._payload is None:
            self.decode_payload()
        if isinstance(payload, ElementClass):
            self._payload.append(XMLPayload(payload))
        elif isinstance(payload, StanzaPayload):
            self._payload.append(payload)
        else:
            raise TypeError("Bad payload type")
        self._dirty = True

    def get_all_payload(self, specialize = False):
        """Return list of stanza payload objects.

        :Parameters:
            - `specialize`: If `True`, then return objects of specialized
              `StanzaPayload` classes whenever possible, otherwise the
              representation already available will be used (often
              `XMLPayload`)

        :Returntype: `list` of `StanzaPayload`
        """
        if self._payload is None:
            self.decode_payload(specialize)
        elif specialize:
            for i, payload in enumerate(self._payload):
                if isinstance(payload, XMLPayload):
                    klass = payload_class_for_element_name(
                                                        payload.element.tag)
                    if klass is not XMLPayload:
                        payload = klass.from_xml(payload.element)
                        self._payload[i] = payload
        return list(self._payload)

    def get_payload(self, payload_class, payload_key = None,
                                                        specialize = False):
        """Get the first payload item matching the given class
        and optional key.

        Payloads may be addressed using a specific payload class or
        via the generic `XMLPayload` element, though the `XMLPayload`
        representation is available only as long as the element is not
        requested by a more specific type.

        :Parameters:
            - `payload_class`: requested payload class, a subclass of
              `StanzaPayload`. If `None` get the first payload in whatever
              class is available.
            - `payload_key`: optional key for additional match. When used
              with `payload_class` = `XMLPayload` this selects the element to
              match
            - `specialize`: If `True`, and `payload_class` is `None` then
              return object of a specialized `StanzaPayload` subclass whenever
              possible
        :Types:
            - `payload_class`: `StanzaPayload`
            - `specialize`: `bool`

        :Return: payload element found or `None`
        :Returntype: `StanzaPayload`
        """
        if self._payload is None:
            self.decode_payload()
        if payload_class is None:
            if self._payload:
                payload = self._payload[0]
                if specialize and isinstance(payload, XMLPayload):
                    klass = payload_class_for_element_name(
                                                        payload.element.tag)
                    if klass is not XMLPayload:
                        payload = klass.from_xml(payload.element)
                        self._payload[0] = payload
                return payload
            else:
                return None
        # pylint: disable=W0212
        elements = payload_class._pyxmpp_payload_element_name
        for i, payload in enumerate(self._payload):
            if isinstance(payload, XMLPayload):
                if payload_class is not XMLPayload:
                    if payload.xml_element_name not in elements:
                        continue
                    payload = payload_class.from_xml(payload.element)
            elif not isinstance(payload, payload_class):
                continue
            if payload_key is not None and payload_key != payload.handler_key():
                continue
            self._payload[i] = payload
            return payload
        return None

    last_id = random.randrange(1000000)

    @classmethod
    def gen_id(cls):
        """Generate stanza id unique for the session.

        :return: the new id."""
        cls.last_id += 1
        return str(cls.last_id) class Message(Stanza):
    """<message /> stanza class.
    """
    # pylint: disable-msg=R0902,R0904
    element_name = "message"
    def __init__(self, element = None, from_jid = None, to_jid = None,
                            stanza_type = None, stanza_id = None,
                            error = None, error_cond = None, return_path = None,
                            language = None,
                            subject = None, body = None, thread = None):
        """Initialize a `Message` object.

        :Parameters:
            - `element`: XML element of this stanza.
            - `from_jid`: sender JID.
            - `to_jid`: recipient JID.
            - `stanza_type`: staza type: one of: "normal", "chat", "headline",
              "error", "groupchat"
            - `stanza_id`: stanza id -- value of stanza's "id" attribute. If
              not given, then unique for the session value is generated.
            - `error_cond`: error condition name. Ignored if `stanza_type`
              is not "error".
            - `language`: default language for the stanza content
            - `subject`: message subject,
            - `body`: message body.
            - `thread`: message thread id.
        :Types:
            - `element`: :etree:`ElementTree.Element`
            - `from_jid`: `JID`
            - `to_jid`: `JID`
            - `stanza_type`: `unicode`
            - `stanza_id`: `unicode`
            - `error`: `pyxmpp.error.StanzaErrorElement`
            - `error_cond`: `unicode`
            - `subject`: `unicode`
            - `body`: `unicode`
            - `thread`: `unicode`
            - `language`: `unicode`
        """
        # pylint: disable-msg=R0913
        self._subject = None
        self._body = None
        self._thread = None
        if element is None:
            element = "message"
        elif not isinstance(element, ElementClass):
            raise TypeError("Couldn't make Message from " + repr(element))

        Stanza.__init__(self, element, from_jid = from_jid, to_jid = to_jid,
                        stanza_type = stanza_type, stanza_id = stanza_id,
                        error = error, error_cond = error_cond,
                        return_path = return_path, language = language)

        if self.element_name != "message":
            raise ValueError("The element is not <message/>")

        self._subject_tag = self._ns_prefix + "subject"
        self._body_tag = self._ns_prefix + "body"
        self._thread_tag = self._ns_prefix + "thread"

        if self._element is not None:
            self._decode_subelements()

        if subject is not None:
            self.subject = subject
        if body is not None:
            self.body = body
        if thread is not None:
            self.thread = thread

    def _decode_subelements(self):
        """Decode the stanza subelements."""
        for child in self._element:
            if child.tag == self._subject_tag:
                self._subject = child.text
            elif child.tag == self._body_tag:
                self._body = child.text
            elif child.tag == self._thread_tag:
                self._thread = child.text

    def as_xml(self):
        """Return the XML stanza representation.

        Always return an independent copy of the stanza XML representation,
        which can be freely modified without affecting the stanza.

        :returntype: :etree:`ElementTree.Element`"""
        result = Stanza.as_xml(self)
        if self._subject:
            child = ElementTree.SubElement(result, self._subject_tag)
            child.text = self._subject
        if self._body:
            child = ElementTree.SubElement(result, self._body_tag)
            child.text = self._body
        if self._thread:
            child = ElementTree.SubElement(result, self._thread_tag)
            child.text = self._thread
        return result

    def copy(self):
        """Create a deep copy of the stanza.

        :returntype: `Message`"""
        result = Message(None, self.from_jid, self.to_jid,
                        self.stanza_type, self.stanza_id, self.error,
                        self._return_path(), self._subject, self._body,
                                                            self._thread)
        for payload in self._payload:
            result.add_payload(payload.copy())
        return result

    @property
    def subject(self): # pylint: disable-msg=E0202
        """Message subject.

        :Returntype: `unicode`
        """
        return self._subject

    @subject.setter # pylint: disable-msg=E1101
    def subject(self, subject): # pylint: disable-msg=E0202,E0102,C0111
        self._subject = unicode(subject)
        self._dirty = True

    @property
    def body(self): # pylint: disable-msg=E0202
        """Message body.

        :Returntype: `unicode`
        """
        return self._body

    @body.setter # pylint: disable-msg=E1101
    def body(self, body): # pylint: disable-msg=E0202,E0102,C0111
        self._body = unicode(body)
        self._dirty = True

    @property
    def thread(self): # pylint: disable-msg=E0202
        """Thread id.

        :Returntype: `unicode`
        """
        return self._thread

    @thread.setter # pylint: disable-msg=E1101
    def thread(self, thread): # pylint: disable-msg=E0202,E0102,C0111
        self._thread = unicode(thread)
        self._dirty = True

    def make_error_response(self, cond):
        """Create error response for any non-error message stanza.

        :Parameters:
            - `cond`: error condition name, as defined in XMPP specification.

        :return: new message stanza with the same "id" as self, "from" and
            "to" attributes swapped, type="error" and containing <error />
            element plus payload of `self`.
        :returntype: `Message`"""

        if self.stanza_type == "error":
            raise ValueError("Errors may not be generated in response"
                                                                " to errors")

        msg = Message(stanza_type = "error", from_jid = self.to_jid,
                        to_jid = self.from_jid, stanza_id = self.stanza_id,
                        error_cond = cond,
                        subject = self._subject, body = self._body,
                        thread = self._thread)

        if self._payload is None:
            self.decode_payload()
        for payload in self._payload:
            msg.add_payload(payload.copy())

        return msg class Presence(Stanza):
    """<presence /> stanza.

    """
    # pylint: disable-msg=R0902,R0904
    element_name = "presence"
    def __init__(self, element = None, from_jid = None, to_jid = None,
                            stanza_type = None, stanza_id = None,
                            error = None, error_cond = None, return_path = None,
                            language = None,
                            show = None, status = None, priority = None):
        """Initialize a `Presence` object.

        :Parameters:
            - `element`: XML element
            - `from_jid`: sender JID.
            - `to_jid`: recipient JID.
            - `stanza_type`: staza type: one of: None, "available",
              "unavailable", "subscribe", "subscribed", "unsubscribe",
              "unsubscribed" or "error". "available" is automatically changed to
              None.
            - `stanza_id`: stanza id -- value of stanza's "id" attribute
            - `language`: default language for the stanza content
            - `show`: "show" field of presence stanza. One of: None, "away",
              "xa", "dnd", "chat".
            - `status`: descriptive text for the presence stanza.
            - `priority`: presence priority.
            - `error_cond`: error condition name. Ignored if `stanza_type` is
              not "error"
        :Types:
            - `element`: :etree:`ElementTree.Element`
            - `from_jid`: `JID`
            - `to_jid`: `JID`
            - `stanza_type`: `unicode`
            - `stanza_id`: `unicode`
            - `language`: `unicode`
            - `show`: `unicode`
            - `status`: `unicode`
            - `priority`: `int`
            - `error_cond`: `unicode`
        """
        # pylint: disable-msg=R0913
        self._show = None
        self._status = None
        self._priority = 0
        if element is None:
            element = "presence"
        elif not isinstance(element, ElementClass):
            raise TypeError("Couldn't make Presence from " + repr(element))

        if stanza_type is not None and stanza_type not in PRESENCE_TYPES:
            raise ValueError("Bad presence type")
        elif stanza_type == 'available':
            stanza_type = None

        Stanza.__init__(self, element, from_jid = from_jid, to_jid = to_jid,
                        stanza_type = stanza_type, stanza_id = stanza_id,
                        error = error, error_cond = error_cond,
                        return_path = return_path, language = language)

        if self.element_name != "presence":
            raise ValueError("The element is not <presence />")

        self._show_tag = self._ns_prefix + "show"
        self._status_tag = self._ns_prefix + "status"
        self._priority_tag = self._ns_prefix + "priority"

        if self._element is not None:
            self._decode_subelements()

        if show is not None:
            self.show = show
        if status is not None:
            self.status = status
        if priority is not None:
            self.priority = priority

    def _decode_subelements(self):
        """Decode the stanza subelements."""
        for child in self._element:
            if child.tag == self._show_tag:
                self._show = child.text
            elif child.tag == self._status_tag:
                self._status = child.text
            elif child.tag == self._priority_tag:
                try:
                    self._priority = int(child.text.strip())
                    if self._priority < -128 or self._priority > 127:
                        raise ValueError
                except ValueError:
                    raise BadRequestProtocolError(
                                            "Presence priority not an integer")

    def as_xml(self):
        """Return the XML stanza representation.

        Always return an independent copy of the stanza XML representation,
        which can be freely modified without affecting the stanza.

        :returntype: :etree:`ElementTree.Element`"""
        result = Stanza.as_xml(self)
        if self._show:
            child = ElementTree.SubElement(result, self._show_tag)
            child.text = self._show
        if self._status:
            child = ElementTree.SubElement(result, self._status_tag)
            child.text = self._status
        if self._priority:
            child = ElementTree.SubElement(result, self._priority_tag)
            child.text = unicode(self._priority)
        return result

    def copy(self):
        """Create a deep copy of the stanza.

        :returntype: `Presence`"""
        result = Presence(None, self.from_jid, self.to_jid,
                        self.stanza_type, self.stanza_id, self.error,
                        self._return_path(),
                        self._show, self._status, self._priority)
        if self._payload is None:
            self.decode_payload()
        for payload in self._payload:
            result.add_payload(payload.copy())
        return result

    @property
    def show(self): # pylint: disable-msg=E0202
        """Presence status type.

        :returntype: `unicode`
        """
        return self._show

    @show.setter # pylint: disable-msg=E1101
    def show(self, show): # pylint: disable-msg=E0202,E0102,C0111
        self._show = unicode(show)
        self._dirty = True

    @property
    def status(self): # pylint: disable-msg=E0202
        """Presence status message.

        :returntype: `unicode`
        """
        return self._status

    @status.setter # pylint: disable-msg=E1101
    def status(self, status): # pylint: disable-msg=E0202,E0102,C0111
        self._status = unicode(status)
        self._dirty = True

    @property
    def priority(self): # pylint: disable-msg=E0202
        """Presence priority.

        :returntype: `unicode`
        """
        return self._priority

    @priority.setter # pylint: disable-msg=E1101
    def priority(self, priority): # pylint: disable-msg=E0202,E0102,C0111
        priority = int(priority)
        if priority < -128 or priority > 127:
            raise ValueError("Priority must be in the (-128, 128) range")
        self._priority = priority
        self._dirty = True

    def make_accept_response(self):
        """Create "accept" response for the "subscribe" / "subscribed" /
        "unsubscribe" / "unsubscribed" presence stanza.

        :return: new stanza.
        :returntype: `Presence`
        """
        if self.stanza_type not in ("subscribe", "subscribed",
                                                "unsubscribe", "unsubscribed"):
            raise ValueError("Results may only be generated for 'subscribe',"
                "'subscribed','unsubscribe' or 'unsubscribed' presence")
        stanza = Presence(stanza_type = ACCEPT_RESPONSES[self.stanza_type],
                            from_jid = self.to_jid, to_jid = self.from_jid,
                                                    stanza_id = self.stanza_id)
        return stanza

    def make_deny_response(self):
        """Create "deny" response for the "subscribe" / "subscribed" /
        "unsubscribe" / "unsubscribed" presence stanza.

        :return: new presence stanza.
        :returntype: `Presence`
        """
        if self.stanza_type not in ("subscribe", "subscribed",
                                                "unsubscribe", "unsubscribed"):
            raise ValueError("Results may only be generated for 'subscribe',"
                "'subscribed','unsubscribe' or 'unsubscribed' presence")
        stanza = Presence(stanza_type = DENY_RESPONSES[self.stanza_type],
                            from_jid = self.to_jid, to_jid = self.from_jid,
                                                    stanza_id = self.stanza_id)
        return stanza

    def make_error_response(self, cond):
        """Create error response for the any non-error presence stanza.

        :Parameters:
            - `cond`: error condition name, as defined in XMPP specification.
        :Types:
            - `cond`: `unicode`

        :return: new presence stanza.
        :returntype: `Presence`
        """

        if self.stanza_type == "error":
            raise ValueError("Errors may not be generated in response"
                                                                " to errors")

        stanza = Presence(stanza_type = "error", from_jid = self.from_jid,
                            to_jid = self.to_jid, stanza_id = self.stanza_id,
                            status = self._status, show = self._show,
                            priority = self._priority, error_cond = cond)

        if self._payload is None:
            self.decode_payload()

        for payload in self._payload:
            stanza.add_payload(payload)

        return stanza class XMLPayload(StanzaPayload):
    """Transparent XML payload for stanza.

    This object can be used for any stanza payload.
    It doesn't decode the XML element, but instead keeps it in the ElementTree
    format.

    :Ivariables:
        - `xml_element_name`: qualified name of the wrapped element
        - `element`: the wrapped element
    :Types:
        - `xml_element_name`: `unicode`
        - `element`: :etree:`ElementTree.Element`
    """
    def __init__(self, data):
        if isinstance(data, StanzaPayload):
            data = data.as_xml()
        if not isinstance(data, ElementClass):
            raise TypeError("ElementTree.Element required")
        self.xml_element_name = data.tag
        self.element = data

    @classmethod
    def from_xml(cls, element):
        return cls(element)

    def as_xml(self):
        return self.element

    @property
    def handler_key(self):
        """Return `xml_element_name` as the extra key for stanza
        handlers."""
        return self.xml_element_name class Iq(Stanza):
    """<message /> stanza class."""
    # pylint: disable-msg=R0902
    element_name = "message"
    def __init__(self, element = None, from_jid = None, to_jid = None,
                            stanza_type = None, stanza_id = None,
                            error = None, error_cond=None, return_path = None,
                            language = None):
        """Initialize an `Iq` object.

        :Parameters:
            - `element`: XML element of this stanza.
            - `from_jid`: sender JID.
            - `to_jid`: recipient JID.
            - `stanza_type`: staza type: one of: "get", "set", "response"
              or "error".
            - `stanza_id`: stanza id -- value of stanza's "id" attribute. If
              not given, then unique for the session value is generated.
            - `error_cond`: error condition name. Ignored if `stanza_type`
              is not "error".
            - `language`: default language for the stanza content
        :Types:
            - `element`: :etree:`ElementTree.Element`
            - `from_jid`: `JID`
            - `to_jid`: `JID`
            - `stanza_type`: `unicode`
            - `stanza_id`: `unicode`
            - `error`: `pyxmpp.error.StanzaErrorElement`
            - `error_cond`: `unicode`
            - `language`: `unicode`
        """
        # pylint: disable-msg=R0913

        if stanza_type is None:
            if element is None:
                raise ValueError("Missing iq type")
        elif stanza_type not in IQ_TYPES:
            raise ValueError("Bad iq type")

        if element is None and stanza_id is None and stanza_type in (
                                                                "get", "set"):
            stanza_id = self.gen_id()

        if element is None:
            element = "iq"
        elif not isinstance(element, ElementClass):
            raise TypeError("Couldn't make Iq from " + repr(element))

        Stanza.__init__(self, element, from_jid = from_jid, to_jid = to_jid,
                        stanza_type = stanza_type, stanza_id = stanza_id,
                        error = error, error_cond = error_cond,
                        return_path = return_path, language = language)


        if self.element_name != "iq":
            raise ValueError("The element is not <iq/>")

    def copy(self):
        """Create a deep copy of the stanza.

        :returntype: `Iq`"""
        result = Iq(None, self.from_jid, self.to_jid,
                        self.stanza_type, self.stanza_id, self.error,
                        self._return_path())
        if self._payload is None:
            self.decode_payload()
        for payload in self._payload:
            # use Stanza.add_payload to skip the payload length check
            Stanza.add_payload(result, payload)
        return result

    def make_error_response(self, cond):
        """Create error response for the a "get" or "set" iq stanza.

        :Parameters:
            - `cond`: error condition name, as defined in XMPP specification.

        :return: new `Iq` object with the same "id" as self, "from" and "to"
            attributes swapped, type="error" and containing <error /> element
            plus payload of `self`.
        :returntype: `Iq`"""

        if self.stanza_type in ("result", "error"):
            raise ValueError("Errors may not be generated for"
                                                " 'result' and 'error' iq")

        stanza = Iq(stanza_type="error", from_jid = self.to_jid,
                        to_jid = self.from_jid, stanza_id = self.stanza_id,
                        error_cond = cond)
        if self._payload is None:
            self.decode_payload()
        for payload in self._payload:
            # use Stanza.add_payload to skip the payload length check
            Stanza.add_payload(stanza, payload)
        return stanza

    def make_result_response(self):
        """Create result response for the a "get" or "set" iq stanza.

        :return: new `Iq` object with the same "id" as self, "from" and "to"
            attributes replaced and type="result".
        :returntype: `Iq`"""

        if self.stanza_type not in ("set", "get"):
            raise ValueError("Results may only be generated for"
                                                        " 'set' or 'get' iq")
        stanza = Iq(stanza_type = "result", from_jid = self.to_jid,
                        to_jid = self.from_jid, stanza_id = self.stanza_id)
        return stanza

    def add_payload(self, payload):
        """Add new the stanza payload. Fails if there is already some
        payload element attached (<iq/> stanza can contain only one payload
        element)

        Marks the stanza dirty.

        :Parameters:
            - `payload`: XML element or stanza payload object to add
        :Types:
            - `payload`: :etree:`ElementTree.Element` or
              `interfaces.StanzaPayload`
        """
        if self._payload is None:
            self.decode_payload()
        if len(self._payload) >= 1:
            raise ValueError("Cannot add more payload to Iq stanza")
        return Stanza.add_payload(self, payload) class XMPPFeatureHandler:
    """Base class for objects handling incoming stanzas.

    The subclasses should provide methods decorated with one of:

      - `iq_get_stanza_handler`: for methods handling ``<iq type="get"/>``
        stanzas.
      - `iq_set_stanza_handler`: for methods handling ``<iq type="set"/>``
        stanzas.
      - `message_stanza_handler`: for methods handling ``<message />`` stanzas
      - `presence_stanza_handler`: for methods handling ``<presence />``
        stanzas

    :Ivariables:
        - `stanza_processor`: a stanza processor where this object was
          registered most recently (injected by `StanzaProcessor`)
    :Types:
        - `stanza_processor`: `StanzaProcessor`
    """
    # pylint: disable-msg=W0232,R0903
    __metaclass__ = ABCMeta
    stanza_processor = None class StanzaRoute:
    """Base class for objects that can send and receive stanzas."""
    # pylint: disable=W0232
    @abstractmethod
    def send(self, stanza):
        """Send stanza through this route."""
        pass

    @abstractmethod
    def uplink_receive(self, stanza):
        """Handle stanza received from 'uplink'."""
        pass
import logging
import threading
import inspect
from collections import defaultdict
from .expdict import ExpiringDictionary
from .exceptions import ProtocolError, BadRequestProtocolError
from .exceptions import ServiceUnavailableProtocolError, NoRouteError
from .stanza import Stanza
from .message import Message
from .presence import Presence
from .stanzapayload import XMLPayload
from .iq import Iq
from .interfaces import XMPPFeatureHandler, StanzaRoute
#
# (C) Copyright 2003-2011 Jacek Konieczny <jajcus@jajcus.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License Version
# 2.1 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

"""Handling of XMPP stanzas.

Normative reference:
  - :RFC:`6129`
"""

from __future__ import absolute_import, division

__docformat__ = "restructuredtext en"




logger = logging.getLogger("pyxmpp2.stanzaprocessor")

def stanza_factory(element, return_path = None, language = None):
    """Creates Iq, Message or Presence object for XML stanza `element`

    :Parameters:
        - `element`: the stanza XML element
        - `return_path`: object through which responses to this stanza should
          be sent (will be weakly referenced by the stanza object).
        - `language`: default language for the stanza
    :Types:
        - `element`: :etree:`ElementTree.Element`
        - `return_path`: `StanzaRoute`
        - `language`: `unicode`
    """
    tag = element.tag
    if tag.endswith("}iq") or tag == "iq":
        return Iq(element, return_path = return_path, language = language)
    if tag.endswith("}message") or tag == "message":
        return Message(element, return_path = return_path, language = language)
    if tag.endswith("}presence") or tag == "presence":
        return Presence(element, return_path = return_path, language = language)
    else:
        return Stanza(element, return_path = return_path, language = language)

class StanzaProcessor(StanzaRoute):
    """Universal stanza handler/router class.

    Provides facilities to set up custom handlers for various types of stanzas.

    :Ivariables:
        - `lock`: lock object used to synchronize access to the
          `StanzaProcessor` object.
        - `me`: local JID.
        - `peer`: remote endpoint JID.
        - `process_all_stanzas`: when `True` then all stanzas received (and
          not only those addressed to `me`) are considered local.
        - `uplink`: object to route outgoing stanzas through
    :Types:
        - `lock`: :std:`threading.RLock`
        - `me`: `JID`
        - `peer`: `JID`
        - `process_all_stanzas`: `bool`
        - `uplink`: `StanzaRoute`
    """
    # pylint: disable-msg=R0902
    def __init__(self, default_timeout = 300):
        """Initialize a `StanzaProcessor` object.

        :Parameters:
            - `default_timeout`: default timeout for IQ response handlers
        """
        self.me = None
        self.peer = None
        self.uplink = None
        self.process_all_stanzas = True

        self._iq_response_handlers = ExpiringDictionary(default_timeout)