configurationengine/source/cone/public/_plugin_reader.py
changeset 0 2e8eeb919028
child 3 e7e0ae78773e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configurationengine/source/cone/public/_plugin_reader.py	Thu Mar 11 17:04:37 2010 +0200
@@ -0,0 +1,568 @@
+#
+# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description:
+#
+
+import copy
+import logging
+import plugin, exceptions, api, utils
+import cone.confml.model
+
+log = logging.getLogger('cone')
+
+# The XML namespace for common ImplML definitions
+COMMON_IMPLML_NAMESPACE = "http://www.symbianfoundation.org/xml/implml/1"
+
+# Name of the marker variable used to mark a feature as a temporary
+# feature
+TEMP_FEATURE_MARKER_VARNAME = '__plugin_temp_feature_marker'
+
+class TempVariableDefinition(object):
+    """
+    Class representing a temporary variable definition in an implementation file.
+    """
+    
+    def __init__(self, ref, type, value):
+        self.ref = ref
+        self.type = type
+        self.value = value
+    
+    def create_feature(self, config):
+        """
+        Add a feature based on this temp feature definition to the given configuration.
+        """
+        if '.' in self.ref:
+            pos = self.ref.rfind('.')
+            ref = self.ref[pos + 1:]
+            namespace = self.ref[:pos]
+        else:
+            ref = self.ref
+            namespace = ''
+        
+        mapping = {'string' : cone.confml.model.ConfmlStringSetting,
+                   'int'    : cone.confml.model.ConfmlIntSetting,
+                   'real'   : cone.confml.model.ConfmlRealSetting,
+                   'boolean': cone.confml.model.ConfmlBooleanSetting}
+        feature = mapping[self.type](ref)
+        setattr(feature, TEMP_FEATURE_MARKER_VARNAME, True)
+        config.add_feature(feature, namespace)
+        
+        value = utils.expand_refs_by_default_view(self.value, config.get_default_view())
+        config.add_data(api.Data(fqr=self.ref, value=value))
+    
+    def __eq__(self, other):
+        if type(self) is type(other):
+            for varname in ('ref', 'type', 'value'):
+                if getattr(self, varname) != getattr(other, varname):
+                    return False
+            return True
+        else:
+            return False
+        
+    def __ne__(self, other):
+        return not (self == other)
+    
+    def __repr__(self):
+        return "TempFeatureDefinition(ref=%r, type=%r, value=%r)" % (self.ref, self.type, self.value)
+
+class TempVariableSequenceDefinition(object):
+    """
+    Class representing a temporary variable sequence definition in an implementation file.
+    """
+    
+    def __init__(self, ref, sub_items):
+        self.ref = ref
+        self.sub_items = sub_items
+    
+    def create_feature(self, config):
+        if '.' in self.ref:
+            pos = self.ref.rfind('.')
+            ref = self.ref[pos + 1:]
+            namespace = self.ref[:pos]
+        else:
+            ref = self.ref
+            namespace = ''
+        
+        # Creature the sequence feature
+        seq_fea = api.FeatureSequence(ref)
+        setattr(seq_fea, TEMP_FEATURE_MARKER_VARNAME, True)
+        config.add_feature(seq_fea, namespace)
+        
+        # Create the sub-features
+        mapping = {'string' : cone.confml.model.ConfmlStringSetting,
+                   'int'    : cone.confml.model.ConfmlIntSetting,
+                   'real'   : cone.confml.model.ConfmlRealSetting,
+                   'boolean': cone.confml.model.ConfmlBooleanSetting}
+        sub_features = []
+        for sub_item in self.sub_items:
+            sub_feature = mapping[sub_item[1]](sub_item[0])
+            seq_fea.add_feature(sub_feature)
+    
+    def __eq__(self, other):
+        if type(self) is type(other):
+            return self.ref == other.ref and self.sub_items == other.sub_items
+        else:
+            return False
+        
+    def __ne__(self, other):
+        return not (self == other)
+    
+    def __repr__(self):
+        return "TempSeqFeatureDefinition(ref=%r, sub_items=%r)" % (self.ref, self.sub_items)
+
+class SettingRefsOverride(object):
+    """
+    Class representing a setting reference override for an implementation.
+    """
+    def __init__(self, refs=None):
+        """
+        @param refs: The reference overrides, can be a list of references or None.
+        """
+        self.refs = refs
+    
+    def get_refs(self):
+        return self.refs
+
+    def __eq__(self, other):
+        if type(self) is type(other):
+            return self.refs == other.refs
+        else:
+            return False
+        
+    def __ne__(self, other):
+        return not (self == other)
+    
+    def __repr__(self):
+        return "SettingRefsOverride(refs=%r)" % self.refs
+
+class CommonImplmlData(object):
+    """
+    Class representing the common ImplML namespace data read from
+    an XML element.
+    """
+    
+    def __init__(self):
+        self.phase = None
+        self.tags = None
+        self.tempvar_defs = []
+        self.setting_refs_override = None
+        self.output_root_dir = None
+        self.output_sub_dir = None
+    
+    def apply(self, impl):
+        """
+        Apply the data on the given implementation instance.
+        """
+        if self.phase:
+            impl.set_invocation_phase(self.phase)
+        if self.tags:
+            impl.set_tags(self.tags)
+        if self.setting_refs_override:
+            # Override the get_refs() method of the implementation
+            impl.get_refs = self.setting_refs_override.get_refs
+            # Override also the has_ref() method in case it is overridden
+            # in the implementation sub-class
+            impl.has_ref = lambda refs: plugin.ImplBase.has_ref(impl, refs)
+        if self.output_root_dir:
+            impl.set_output_root_override(self.output_root_dir)
+        if self.output_sub_dir:
+            impl.output_subdir = self.output_sub_dir
+    
+    def extend(self, other):
+        """
+        Extend this object with the contents of another CommonImplmlData object.
+        """
+        if other.phase:
+            self.phase = other.phase
+        if other.tags:
+            self.tags = other.tags
+        self.tempvar_defs.extend(other.tempvar_defs)
+        if other.setting_refs_override:
+            self.setting_refs_override = other.setting_refs_override
+        if other.output_root_dir:
+            self.output_root_dir = other.output_root_dir
+        if other.output_sub_dir:
+            self.output_sub_dir = other.output_sub_dir
+    
+    def copy(self):
+        result = CommonImplmlData()
+        result.phase = self.phase
+        if result.tags is not None:
+            result.tags = self.tags.copy()
+        result.tempvar_defs = list(self.tempvar_defs)
+        result.setting_refs_override = copy.deepcopy(self.setting_refs_override)
+        result.output_root_dir = self.output_root_dir
+        result.output_sub_dir = self.output_sub_dir
+        return result
+    
+    def __eq__(self, other):
+        if type(self) is type(other):
+            for varname in ('phase', 'tags', 'tempvar_defs', 'setting_refs_override', 'output_root_dir', 'output_sub_dir'):
+                if getattr(self, varname) != getattr(other, varname):
+                    return False
+            return True
+        else:
+            return False
+    
+    def __ne__(self, other):
+        return not (self == other)
+    
+    def __repr__(self):
+        return "CommonImplmlData(phase=%r, tags=%r, tempvar_defs=%r, setting_refs_override=%r, output_root_dir=%r, output_sub_dir=%r)" \
+            % (self.phase,
+               self.tags,
+               self.tempvar_defs,
+               self.setting_refs_override,
+               self.output_root_dir,
+               self.output_sub_dir)
+
+class ImplReader(object):
+    """
+    Internal reader class for reading implementations from a file in a configuration.
+    """
+    
+    # The reader class list loaded using ImplFactory
+    __loaded_reader_classes = None
+    __reader_classes = None
+    __supported_file_extensions = None
+    __ignored_namespaces = None
+    
+    def __init__(self, resource_ref, configuration):
+        self.resource_ref = resource_ref
+        self.configuration = configuration
+    
+    @classmethod
+    def _load_data_from_plugins(cls):
+        """
+        Load all data needed for implementation parsing from the plug-ins.
+        
+        The actual loading is only done the first time this method is called.
+        """
+        # Load the data only if the reader class list has not been loaded
+        # yet or it has changed
+        loaded_reader_classes = plugin.ImplFactory.get_reader_classes()
+        if cls.__loaded_reader_classes is loaded_reader_classes:
+            return
+        
+        reader_classes = [plugin.ReaderBase]
+        reader_classes.extend(loaded_reader_classes)
+        
+        cls.__reader_classes = {}
+        cls.__ignored_namespaces = []
+        cls.__supported_file_extensions = []
+        
+        for rc in reader_classes:
+            # Reader class
+            ns = rc.NAMESPACE
+            if ns is not None:
+                if ns in cls.__reader_classes:
+                    raise RuntimeError("Multiple reader classes registered for ImplML namespace '%s': at least %s and %s"\
+                                       % (ns, rc, cls.__reader_classes[ns]))
+                cls.__reader_classes[ns] = rc
+            
+            # Ignored namespaces
+            for ns in rc.IGNORED_NAMESPACES:
+                if ns not in cls.__ignored_namespaces:
+                    cls.__ignored_namespaces.append(ns)
+            
+            # Supported file extensions
+            for fe in rc.FILE_EXTENSIONS:
+                fe = fe.lower()
+                if fe not in cls.__supported_file_extensions:
+                    cls.__supported_file_extensions.append(fe)
+            
+        cls.__loaded_reader_classes = loaded_reader_classes
+    
+    @classmethod
+    def _get_namespaces(cls, etree):
+        """
+        Return a list of XML namespaces in the given element tree.
+        """
+        namespaces = []
+        namespaces.append(utils.xml.split_tag_namespace(etree.tag)[0])
+        for elem in etree:
+            ns = utils.xml.split_tag_namespace(elem.tag)[0]
+            if ns not in namespaces:
+                namespaces.append(ns)
+        return filter(lambda ns: ns is not None, namespaces)
+    
+    def _read_impls_from_file_root_element(self, root, namespaces):
+        impls = []
+        reader_classes = self.get_reader_classes()
+        
+        # Go through the list of XML namespaces encountered in the
+        # file and read an implementation using the corresponding
+        # reader for each namespace
+        impl_count = 0
+        common_data = CommonImplmlDataReader.read_data(root)
+        for ns in namespaces:
+            if ns not in reader_classes: continue
+            
+            rc = reader_classes[ns]
+            impl = self._read_impl(rc, root)
+            if impl:
+                impl.index = impl_count
+                impl_count += 1
+                if common_data: common_data.apply(impl)
+                impls.append(impl)
+        
+        # Add temp feature definitions to the first implementation
+        if common_data and impls:
+            impls[0]._tempvar_defs.extend(common_data.tempvar_defs)
+        return impls
+    
+    def _read_impls_from_file_sub_elements(self, root):
+        impls = []
+        
+        # Collect common ImplML namespace data
+        common_data = CommonImplmlData()
+        for elem in root:
+            ns = utils.xml.split_tag_namespace(elem.tag)[0]
+            if ns == COMMON_IMPLML_NAMESPACE:
+                cd = CommonImplmlDataReader.read_data(elem)
+                if cd: common_data.extend(cd)
+        
+        # Go through all sub-elements and read an implementation instance
+        # from each if possible
+        impl_count = 0
+        reader_classes = self.get_reader_classes()
+        for elem in root:
+            ns = utils.xml.split_tag_namespace(elem.tag)[0]
+            if ns != COMMON_IMPLML_NAMESPACE and ns in reader_classes:
+                reader_class = reader_classes[ns]
+                impl = self._read_impl(reader_class, elem)
+                if impl:
+                    cd = CommonImplmlDataReader.read_data(elem)
+                    if cd is not None:
+                        impl._tempvar_defs.extend(cd.tempvar_defs)
+                        data = common_data.copy()
+                        data.extend(cd)
+                        data.apply(impl)
+                    else:
+                        common_data.apply(impl)
+                    
+                    impl.index = impl_count
+                    impl_count += 1
+                    impls.append(impl)
+        
+        # Add temporary feature definitions to the first implementation instance
+        if impls:
+            impls[0]._tempvar_defs = common_data.tempvar_defs + impls[0]._tempvar_defs
+        
+        return impls
+    
+    def _read_impl(self, reader_class, elem):
+        """
+        Read an implementation with the given reader class from the given element.
+        
+        If an exception is raised during reading, the exception is logged
+        and None returned. 
+        
+        @return: The read implementation or None.
+        """
+        try:
+            return reader_class.read_impl(self.resource_ref, self.configuration, elem)
+        except exceptions.ParseError, e:
+            log.error("Error reading implementation '%s': %s", (self.resource_ref, e))
+        except Exception, e:
+            utils.log_exception(log, e)
+            
+        return None
+
+    @classmethod
+    def get_reader_classes(cls):
+        """
+        Return a dictionary of all possible implementation reader classes.
+        
+        Dictionary key is the XML namespace and the value is the corresponding
+        reader class.
+        """
+        cls._load_data_from_plugins()
+        return cls.__reader_classes
+    
+    @classmethod
+    def get_supported_file_extensions(cls):
+        """
+        Return a list of all supported implementation file extensions.
+        """
+        cls._load_data_from_plugins()
+        return cls.__supported_file_extensions
+    
+    @classmethod
+    def get_ignored_namespaces(cls):
+        """
+        Return a list of all ignored XML namespaces.
+        """
+        cls._load_data_from_plugins()
+        return cls.__ignored_namespaces
+
+    def read_implementations(self):
+        try:
+            root = plugin.ReaderBase._read_xml_doc_from_resource(self.resource_ref, self.configuration)
+            return self.read_implementation(root)
+        except exceptions.ParseError, e:
+            # Invalid XML data in the file
+            log.error(e)
+            return []
+
+    def read_implementation(self, xmlroot):
+        root = xmlroot
+        
+        # Check if the implementations should all be read from the
+        # document root, or each from its own sub-element under the root
+        read_from_root = False
+        ns = utils.xml.split_tag_namespace(root.tag)[0]
+        if ns: read_from_root = True
+        
+        # Collect namespaces from the file and check that all are supported or ignored
+        namespaces = self._get_namespaces(root)
+        for ns in namespaces:
+            if ns != COMMON_IMPLML_NAMESPACE \
+                and ns not in self.get_reader_classes() \
+                and ns not in self.get_ignored_namespaces():
+                log.error("Unsupported XML namespace '%s' in file '%s'" % (ns, self.resource_ref))
+                return []
+        
+        if read_from_root:
+            impls = self._read_impls_from_file_root_element(root, namespaces)
+        else:
+            impls = self._read_impls_from_file_sub_elements(root)
+        return impls
+
+
+class CommonImplmlDataReader(object):
+    """
+    Internal reader class for reading common ImplML namespace data from and element.
+    """
+    
+    VALID_PHASES = ('pre', 'normal', 'post')
+    VALID_TYPES = ('string', 'int', 'real', 'boolean')
+    
+    @classmethod
+    def read_data(cls, etree):
+        """
+        Read common ImplML data from the given XML element.
+        @return: A CommonImplmlData instance or None if no common namespace
+            elements were found.
+        """
+        result = CommonImplmlData()
+        
+        reader_methods = {'phase'                   : cls._read_phase,
+                          'tag'                     : cls._read_tag,
+                          'tempVariable'            : cls._read_tempvar,
+                          'tempVariableSequence'    : cls._read_tempvarseq,
+                          'settingRefsOverride'     : cls._read_setting_refs_override,
+                          'outputRootDir'           : cls._read_output_root_dir,
+                          'outputSubDir'            : cls._read_output_sub_dir}
+        
+        found = False
+        for elem in etree:
+            ns, tag = utils.xml.split_tag_namespace(elem.tag)
+            if ns != COMMON_IMPLML_NAMESPACE:   continue
+            if tag not in reader_methods:       continue
+            
+            reader_methods[tag](elem, result)
+            found = True
+        
+        if found:   return result
+        else:       return None
+    
+    @classmethod
+    def _read_phase(cls, elem, result):
+        phase = elem.get('name')
+        if phase is None:
+            cls._raise_missing_attr(elem, 'name')
+        if phase not in cls.VALID_PHASES:
+            raise exceptions.ParseError("Invalid invocation phase '%s' defined." % phase)
+        
+        result.phase = phase
+    
+    @classmethod
+    def _read_tag(cls, elem, result):
+        name = elem.get('name')
+        value = elem.get('value')
+        if name is not None:
+            if result.tags is None:     result.tags = {}
+            if name not in result.tags: result.tags[name] = []
+            result.tags[name].append(value)
+    
+    @classmethod
+    def _read_tempvar(cls, elem, result):
+        ref = elem.get('ref')
+        type = elem.get('type', 'string')
+        value = elem.get('value', '')
+        
+        if ref is None:
+            cls._raise_missing_attr(elem, 'ref')
+        if type not in cls.VALID_TYPES:
+            cls._raise_invalid_type(ref, type)
+        
+        result.tempvar_defs.append(TempVariableDefinition(ref, type, value))
+    
+    @classmethod
+    def _read_tempvarseq(cls, elem, result):
+        ref = elem.get('ref')
+        if ref is None:
+            cls._raise_missing_attr(elem, 'ref')
+        
+        sub_items = []
+        for sub_elem in elem.findall('{%s}tempVariable' % COMMON_IMPLML_NAMESPACE):
+            sub_ref = sub_elem.get('ref')
+            sub_type = sub_elem.get('type', 'string')
+            
+            if sub_ref is None:
+                cls._raise_missing_attr(sub_elem, 'ref')
+            if sub_type not in cls.VALID_TYPES:
+                cls._raise_invalid_type(sub_ref, sub_type)
+            
+            sub_items.append((sub_ref, sub_type))
+        
+        if not sub_items:
+            raise exceptions.ParseError("Temporary variable sequence '%s' does not have any sub-items" % ref)
+        
+        result.tempvar_defs.append(TempVariableSequenceDefinition(ref, sub_items))
+    
+    @classmethod
+    def _read_setting_refs_override(cls, elem, result):
+        if elem.get('refsIrrelevant', 'false').lower() in ('1', 'true'):
+            refs = None
+        else:
+            refs = []
+            for sub_elem in elem.findall('{%s}settingRef' % COMMON_IMPLML_NAMESPACE):
+                ref = sub_elem.get('value')
+                
+                if ref is None:
+                    cls._raise_missing_attr(sub_elem, 'value')
+                
+                refs.append(ref)
+                
+        result.setting_refs_override = SettingRefsOverride(refs)
+    
+    @classmethod
+    def _read_output_root_dir(cls, elem, result):
+        value = elem.get('value')
+        if value: result.output_root_dir = value
+    
+    @classmethod
+    def _read_output_sub_dir(cls, elem, result):
+        value = elem.get('value')
+        if value: result.output_sub_dir = value
+
+    @classmethod
+    def _raise_missing_attr(cls, elem, attrname):
+        raise exceptions.ParseError("XML element %s does not contain the mandatory '%s' attribute." % (elem.tag, attrname))
+    
+    @classmethod
+    def _raise_invalid_type(cls, ref, type):
+        raise exceptions.ParseError("Invalid feature type '%s' specified for temporary ConfML feature '%s'." % (type, ref))
\ No newline at end of file