text: # -*- coding: utf-8 -*-
  
  import types
  
  from cloudio.endpoint.attribute import CloudioAttribute
  from cloudio.endpoint.exception.cloudio_modification_exception import CloudioModificationException
  from cloudio.endpoint.exception.invalid_cloudio_attribute_exception import InvalidCloudioAttributeException
  from cloudio.endpoint.interface.attribute_container import CloudioAttributeContainer
  from cloudio.endpoint.interface.object_container import CloudioObjectContainer
  from cloudio.endpoint.topicuuid import TopicUuid
  
  
  class CloudioObject(object):
      """Base class for all cloud.iO objects.
  
      An object can either contain attributes (CloudioAttribute, @StaticAttribute) or child
      objects. Using this it is possible to create data models with a great flexibility.
  
      An object can be annotated with the @Conforms annotation to conform to a cloud.io class. A class in cloud.io is just
      a scheme what attributes and child objects an object has to have. An object is conform to such a scheme if it
      matches exactly the structure of the class. It can not contain more attributes or child objects, then it would be
      not anymore conform to that class.
      """
  
      def __init__(self):
          super(CloudioObject, self).__init__()
          self._internal = _InternalObject(self)
  
      def get_name(self):
          return self._internal.get_name()
  
      def set_name(self, name):
          self._internal.set_name(name)
  
      def find_attribute(self, location):
          return self._internal.find_attribute(location)
  
      def find_object(self, location):
          return self._internal.find_object(location)
  
      def get_attributes(self):
          return self._internal.get_attributes()
  
      def attribute_has_changed_by_endpoint(self, attribute):
          self._internal.attribute_has_changed_by_endpoint(attribute)
  
      def attribute_has_changed_by_cloud(self, attribute):
          self._internal.attribute_has_changed_by_cloud(attribute)
  
      def is_node_registered_within_endpoint(self):
          return self._internal.is_node_registered_within_endpoint()
  
      def get_parent_object_container(self):
          return self._internal.get_parent_object_container()
  
  
  class _InternalObject(CloudioObjectContainer, CloudioAttributeContainer):
  
      def __init__(self, external_object):
          super(_InternalObject, self).__init__()
          self._externalObject = external_object
          self.parent = None  # type: CloudioObjectContainer or None
          self.name = None  # type: str or None
          self.conforms = None
          self.objects = {}
          self._attributes = {}
          self._staticAttributesAdded = False
  
          # Check each field of the actual CloudioObject object.
  
      #        for field in dir(external_object):
      #            # Check if it is an attribute and go get it
      #            attr = getattr(external_object, field)
      #           if attr:
      #               if isinstance(attr, CloudioObject):
      #                  print('Got an attribute based on an CloudioObject class')
      #               elif type(field) == CloudioAttribute:
      #                   print('Got an attribute based on an CloudioAttribute class')
      #               else:
      #                   print('Got an attribute with non-relevant type')
  
      def get_external_object(self):
          return self._externalObject
  
      ######################################################################
      # Interface implementations
      #
      def get_uuid(self):
          # TODO Store topic uuid as attribute
          return TopicUuid(self)
  
      def get_name(self):
          return self.name
  
      def set_name(self, name):
          # If the object already has a name (we are renaming the object)
          # then fail with a runtime exception.
          if self.name:
              raise CloudioModificationException('The Object has already a name (Renaming objects is forbidden)!')
  
          # Set the local name
          self.name = name
  
      def attribute_has_changed_by_endpoint(self, attribute):
          if self.parent:
              self.parent.attribute_has_changed_by_endpoint(attribute)
  
      def attribute_has_changed_by_cloud(self, attribute):
          if self.parent:
              self.parent.attribute_has_changed_by_cloud(attribute)
  
      def is_node_registered_within_endpoint(self):
          return self.parent and self.parent.is_node_registered_within_endpoint()
  
      def get_objects(self):
          return self.objects
  
      def set_parent_object_container(self, object_container):
          # If the object already has a parent (we are moving the object)
          # then fail with a runtime exception.
          if self.parent:
              raise CloudioModificationException('The parent of an Object can never be changed ' +
                                                 '(Objects can not be moved)!')
  
          # Set the parent
          self.parent = object_container
  
      def get_parent_node_container(self):
          return None
  
      def get_parent_object_container(self):
          return self.parent
  
      def set_parent_node_container(self, node_container):
          raise CloudioModificationException('As this is not a node, it can not be embedded into a node container!')
  
      def find_attribute(self, location) -> CloudioAttribute or None:
          """Searches for an attribute.
  
          :param location: List containing the 'topic levels' constructed out of the topic uuid identifying the attribute.
          :type location [str]
          :return: The cloudio object found or None
          """
          if location:
              if len(location) > 0:
                  if location[-1] == 'objects':  # Compare with last element (peek element)
                      location.pop()  # Remove last item
                      if len(location) > 0:
                          if location[-1] in self.get_objects():
                              obj = self.get_objects()[location.pop()]
                              if obj:
                                  return obj.find_attribute(location)
                  elif location[-1] == 'attributes':
                      self.get_attributes()  # Update attributes list
                      location.pop()  # Remove last item
                      if len(location) > 0:
                          if location[-1] in self.get_attributes():
                              attribute = self.get_attributes()[location.pop()]
                              if attribute:
                                  return attribute
          return None
  
      def find_object(self, location):
          """Searches for an object.
  
          :param location: List containing the 'topic levels' constructed out of the topic uuid identifying the object.
          :type location [str]
          :return: The cloudio object found or None
          """
          if location:
              if len(location) > 0:
                  if location[-1] == 'objects':  # Compare with last element (peek element)
                      location.pop()  # Remove last item
                      if len(location) > 0:
                          if location[-1] in self.get_objects():
                              obj = self.get_objects()[location.pop()]
                              return obj
                  elif location[-1] == 'attributes':
                      obj = self.get_attributes()  # Update attributes list
                      location.pop()  # Remove last item
                      return obj
          return None
  
      def get_attributes(self):
          """Returns the contained attributes in this object as a list
          :return A dictionary of attributes
          :rtype {CloudioAttribute}
          """
          # If it is the first call, get all attributes of the CloudioObject
          # and put it into the 'attributes' attribute
          if not self._staticAttributesAdded:
              # Check each field of the actual CloudioObject object.
              for field in dir(self._externalObject):
                  # Check if it is an attribute and go get it
                  attr = getattr(self._externalObject, field)
                  if attr:
                      # Check if it is a static value (means an attribute
                      # with a standard type)
                      #
                      # Check if it is a bool, int, float, string
                      if isinstance(attr, bool) or \
                              isinstance(attr, int) or \
                              isinstance(attr, float) or \
                              isinstance(attr, bytes) or \
                              isinstance(attr, str):
  
                          if field not in ('__module__', '__doc__'):  # Some excludes:
                              attribute = CloudioAttribute()
                              attribute.set_constraint('static')
                              attribute.set_name(field)
                              attribute.set_parent(self)
                              attribute.set_static_value(attr)
  
                              topicUuid = attribute.get_uuid().to_string()
                              if topicUuid and topicUuid not in self._attributes:
                                  self._attributes[topicUuid] = attribute
                              else:
                                  raise CloudioModificationException('Duplicate name for fields')
  
                      elif isinstance(attr, types.MethodType) or \
                              type(attr) or \
                              isinstance(attr, _InternalObject):
                          pass
                      else:
                          raise InvalidCloudioAttributeException(type(attr))
  
              self._staticAttributesAdded = True
  
          return self._attributes
  
      def to_json(self, encoder):
          """Pick out the attributes we want to store / publish.
          """
          attrDict = {}
  
          if self.conforms is not None and len(self.conforms) > 0:
              attrDict['conforms'] = self.conforms
  
          if hasattr(self, 'objects') and len(self.objects) > 0:
              attrDict['objects'] = self.objects
  
          if hasattr(self, '_attributes') and len(self._attributes) > 0:
              attrDict['attributes'] = self._attributes
  
          return encoder.default(attrDict)
  
      ######################################################################
      # Private methods
      #
      @staticmethod
      def _get_conforms():
          return None
  
      def _set_conforms(self, data_class):
          pass
  