# Source code for taurus.core.resource.resfactory

    #!/usr/bin/env python

    # ###########################################################################
    #
    # This file is part of Taurus
    #
    # http://taurus-scada.org
    #
    # Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
    #
    # Taurus is free software: you can redistribute it and/or modify
    # it under the terms of the GNU Lesser General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # Taurus 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 Taurus.  If not, see <http://www.gnu.org/licenses/>.
    #
    # ###########################################################################

    """
    resfactory.py:
    """

    import os
    import importlib
    from collections.abc import Mapping
    from taurus import Manager
    from taurus.core.util.singleton import Singleton
    from taurus.core.util.log import Logger
    from taurus.core.taurusfactory import TaurusFactory
    from taurus.core.taurusexception import TaurusException

    class ResourcesFactory(Singleton, TaurusFactory, Logger):
        """A Singleton class designed to provide Simulation related objects."""

        #: the list of schemes that this factory supports. For this factory: 'res'
        #: and 'resources' are the supported schemes
        schemes = (
            "res",
            "resource",
        )

        #: the default resource file name
        DftResourceName = "taurus_resources.py"

        #: priority for the default resource
        DftResourcePriority = 10

        def __init__(self):
            """Initialization. Nothing to be done here for now."""
            pass

        def init(self, *args, **kwargs):
            """Singleton instance initialization.
            **For internal usage only**
            """
            name = self.__class__.__name__
            self.call__init__(Logger, name)
            self.call__init__(TaurusFactory)
            self.clear()

        def clear(self):
            self._resource_map = {}
            self._resource_priority = {}
            self._resource_priority_keys = []
            self._resource_count = 0

        def reloadResource(self, obj=None, priority=1, name=None):
            """(Re)Loads the given resource.

            :param obj: the resource object. Default is None meaning in will
                (re)load the default resource: taurus_resources.py from the
                application directory
            :type obj: dict or file or None
            :param priority: the resource priority. Default is 1 meaning maximum
                priority
            :type priority: int
            :param name: an optional name to give to the resource
            :type name: str
            :return: a dictionary version of the given resource object
            :rtype: dict
            """
            if priority < 1:
                raise ValueError("priority must be >=1")
            if isinstance(obj, Mapping):
                name = name or "DICT%02d" % priority
            elif type(obj) in (str,) or obj is None:
                name, mod = self.__reloadResource(obj)
                obj = {}
                for k, v in mod.__dict__.items():
                    if not k.startswith("_") and isinstance(v, str):
                        obj[k] = v
            else:
                raise TypeError

            if self._resource_map.get(name) is None:
                self._resource_count += 1
            self._resource_map[name] = obj
            if self._resource_count == 1:
                self._first_resource = obj

            pl = self._resource_priority.get(priority)
            if pl is None:
                self._resource_priority[priority] = pl = []
            pl.append(name)
            self._resource_priority_keys = list(self._resource_priority.keys())
            self._resource_priority_keys.sort()
            return obj

        loadResource = reloadResource
        loadResource.__doc__ = reloadResource.__doc__

        def __reloadResource(self, name=None):
            path = os.path.curdir
            if name is None:
                file_name = ResourcesFactory.DftResourceName
            else:
                path, file_name = os.path.split(name)
                if not path:
                    path = os.path.curdir
            path = os.path.abspath(path)

            full_name = os.path.join(path, file_name)

            if not os.path.isfile(full_name):
                raise ImportError

            module_name, ext = os.path.splitext(file_name)

            # import module from path
            _spec = importlib.util.spec_from_file_location(module_name, full_name)
            m = importlib.util.module_from_spec(_spec)
            _spec.loader.exec_module(m)

            return full_name, m

        def getValue(self, key):
            """Returns the value for a given key

            :param key: a key
            :type key: str
            :return: the value for the given key
            :rtype: str
            """
            if self._resource_count == 0:
                try:
                    self.reloadResource(priority=self.DftResourcePriority)
                except Exception:
                    return None

            # optimization: many applications contain only one resource: in that
            # case avoid the loop
            if self._resource_count == 1:
                return self._first_resource.get(key, None)

            for p in self._resource_priority_keys:
                for resource_name in self._resource_priority[p]:
                    resource = self._resource_map[resource_name]
                    try:
                        return resource[key]
                    except Exception:
                        pass

        def findObjectClass(self, absolute_name):
            """
            Obtain the class object corresponding to the given name.

            :param absolute_name: the object absolute name string
            :type absolute_name: str
            :return: the class for the model object mapped by absolute_name, or
                None if absolute_name is invalid.
            :rtype: taurus.core.taurusmodel.TaurusModel or None
            """
            validators = (
                self.getAttributeNameValidator(),
                self.getDeviceNameValidator(),
                self.getAuthorityNameValidator(),
            )

            for v in validators:
                try:
                    value = self.getValue(
                        v.getUriGroups(absolute_name)["_resname"]
                    )
                    return Manager().findObjectClass(value)
                except Exception:
                    pass
            return None

        def getAuthority(self, name=None):
            """
            Obtain the authority model object referenced by name.

            :param name: name
            :type name: str
            :return: authority object :raise:
                (taurus.core.taurusexception.TaurusException) if name is invalid
            :rtype: taurus.core.taurusauthority.TaurusAuthority
            """
            groups = self.getAuthorityNameValidator().getUriGroups(name)
            if groups is None:
                raise TaurusException('Invalid name "%s"' % name)
            res_name = groups["_resname"]
            value = self.getValue(res_name)
            return Manager().getAuthority(value)

        def getDevice(self, name):
            """
            Obtain the device model object referenced by name.

            :param name: name
            :type name: str
            :return: device object :raise:
                (taurus.core.taurusexception.TaurusException) if name is invalid
            :rtype: taurus.core.taurusdevice.TaurusDevice
            """
            groups = self.getDeviceNameValidator().getUriGroups(name)
            if groups is None:
                raise TaurusException('Invalid name "%s"' % name)
            res_name = groups["_resname"]
            value = self.getValue(res_name)
            return Manager().getDevice(value)

        def getAttribute(self, name):
            """
            Obtain the attribute model object referenced by name.

            :param name: name
            :type name: str
            :return: attribute object :raise:
                (taurus.core.taurusexception.TaurusException) if name is invalid
            :rtype: taurus.core.taurusattribute.TaurusAttribute
            """
            groups = self.getAttributeNameValidator().getUriGroups(name)
            if groups is None:
                raise TaurusException('Invalid name "%s"' % name)
            res_name = groups["_resname"]
            value = self.getValue(res_name)
            return Manager().getAttribute(value)

        def getAuthorityNameValidator(self):
            """Return ResourceAuthorityNameValidator"""
            from . import resvalidator

            return resvalidator.ResourceAuthorityNameValidator()

        def getDeviceNameValidator(self):
            """Return ResourceDeviceNameValidator"""
            from . import resvalidator

            return resvalidator.ResourceDeviceNameValidator()

        def getAttributeNameValidator(self):
            """Return ResourceAttributeNameValidator"""
            from . import resvalidator

            return resvalidator.ResourceAttributeNameValidator()