#
# 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
from cone.public import exceptions, api, utils
import plugin
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, lineno=None):
self.ref = ref
self.type = type
self.value = value
self.lineno = lineno
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}
# Create temp variables always name being also the ref
feature = mapping[self.type](ref, name=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, lineno=None):
self.ref = ref
self.sub_items = sub_items
self.lineno = lineno
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
# Create temp variables always name being also the ref
seq_fea = api.FeatureSequence(ref, name=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], name=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 is None:
return
if other.phase:
self.phase = other.phase
if other.tags:
self.tags = other.tags
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 self.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.
"""
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}
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)
return result
@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', '')
lineno = utils.etree.get_lineno(elem)
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, lineno))
@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)
lineno = utils.etree.get_lineno(elem)
result.tempvar_defs.append(TempVariableSequenceDefinition(ref, sub_items, lineno))
@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))