--- /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