|
1 # |
|
2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 # All rights reserved. |
|
4 # This component and the accompanying materials are made available |
|
5 # under the terms of "Eclipse Public License v1.0" |
|
6 # which accompanies this distribution, and is available |
|
7 # at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 # |
|
9 # Initial Contributors: |
|
10 # Nokia Corporation - initial contribution. |
|
11 # |
|
12 # Contributors: |
|
13 # |
|
14 # Description: |
|
15 # |
|
16 |
|
17 import copy |
|
18 import logging |
|
19 import plugin, exceptions, api, utils |
|
20 import cone.confml.model |
|
21 |
|
22 log = logging.getLogger('cone') |
|
23 |
|
24 # The XML namespace for common ImplML definitions |
|
25 COMMON_IMPLML_NAMESPACE = "http://www.symbianfoundation.org/xml/implml/1" |
|
26 |
|
27 # Name of the marker variable used to mark a feature as a temporary |
|
28 # feature |
|
29 TEMP_FEATURE_MARKER_VARNAME = '__plugin_temp_feature_marker' |
|
30 |
|
31 class TempVariableDefinition(object): |
|
32 """ |
|
33 Class representing a temporary variable definition in an implementation file. |
|
34 """ |
|
35 |
|
36 def __init__(self, ref, type, value): |
|
37 self.ref = ref |
|
38 self.type = type |
|
39 self.value = value |
|
40 |
|
41 def create_feature(self, config): |
|
42 """ |
|
43 Add a feature based on this temp feature definition to the given configuration. |
|
44 """ |
|
45 if '.' in self.ref: |
|
46 pos = self.ref.rfind('.') |
|
47 ref = self.ref[pos + 1:] |
|
48 namespace = self.ref[:pos] |
|
49 else: |
|
50 ref = self.ref |
|
51 namespace = '' |
|
52 |
|
53 mapping = {'string' : cone.confml.model.ConfmlStringSetting, |
|
54 'int' : cone.confml.model.ConfmlIntSetting, |
|
55 'real' : cone.confml.model.ConfmlRealSetting, |
|
56 'boolean': cone.confml.model.ConfmlBooleanSetting} |
|
57 feature = mapping[self.type](ref) |
|
58 setattr(feature, TEMP_FEATURE_MARKER_VARNAME, True) |
|
59 config.add_feature(feature, namespace) |
|
60 |
|
61 value = utils.expand_refs_by_default_view(self.value, config.get_default_view()) |
|
62 config.add_data(api.Data(fqr=self.ref, value=value)) |
|
63 |
|
64 def __eq__(self, other): |
|
65 if type(self) is type(other): |
|
66 for varname in ('ref', 'type', 'value'): |
|
67 if getattr(self, varname) != getattr(other, varname): |
|
68 return False |
|
69 return True |
|
70 else: |
|
71 return False |
|
72 |
|
73 def __ne__(self, other): |
|
74 return not (self == other) |
|
75 |
|
76 def __repr__(self): |
|
77 return "TempFeatureDefinition(ref=%r, type=%r, value=%r)" % (self.ref, self.type, self.value) |
|
78 |
|
79 class TempVariableSequenceDefinition(object): |
|
80 """ |
|
81 Class representing a temporary variable sequence definition in an implementation file. |
|
82 """ |
|
83 |
|
84 def __init__(self, ref, sub_items): |
|
85 self.ref = ref |
|
86 self.sub_items = sub_items |
|
87 |
|
88 def create_feature(self, config): |
|
89 if '.' in self.ref: |
|
90 pos = self.ref.rfind('.') |
|
91 ref = self.ref[pos + 1:] |
|
92 namespace = self.ref[:pos] |
|
93 else: |
|
94 ref = self.ref |
|
95 namespace = '' |
|
96 |
|
97 # Creature the sequence feature |
|
98 seq_fea = api.FeatureSequence(ref) |
|
99 setattr(seq_fea, TEMP_FEATURE_MARKER_VARNAME, True) |
|
100 config.add_feature(seq_fea, namespace) |
|
101 |
|
102 # Create the sub-features |
|
103 mapping = {'string' : cone.confml.model.ConfmlStringSetting, |
|
104 'int' : cone.confml.model.ConfmlIntSetting, |
|
105 'real' : cone.confml.model.ConfmlRealSetting, |
|
106 'boolean': cone.confml.model.ConfmlBooleanSetting} |
|
107 sub_features = [] |
|
108 for sub_item in self.sub_items: |
|
109 sub_feature = mapping[sub_item[1]](sub_item[0]) |
|
110 seq_fea.add_feature(sub_feature) |
|
111 |
|
112 def __eq__(self, other): |
|
113 if type(self) is type(other): |
|
114 return self.ref == other.ref and self.sub_items == other.sub_items |
|
115 else: |
|
116 return False |
|
117 |
|
118 def __ne__(self, other): |
|
119 return not (self == other) |
|
120 |
|
121 def __repr__(self): |
|
122 return "TempSeqFeatureDefinition(ref=%r, sub_items=%r)" % (self.ref, self.sub_items) |
|
123 |
|
124 class SettingRefsOverride(object): |
|
125 """ |
|
126 Class representing a setting reference override for an implementation. |
|
127 """ |
|
128 def __init__(self, refs=None): |
|
129 """ |
|
130 @param refs: The reference overrides, can be a list of references or None. |
|
131 """ |
|
132 self.refs = refs |
|
133 |
|
134 def get_refs(self): |
|
135 return self.refs |
|
136 |
|
137 def __eq__(self, other): |
|
138 if type(self) is type(other): |
|
139 return self.refs == other.refs |
|
140 else: |
|
141 return False |
|
142 |
|
143 def __ne__(self, other): |
|
144 return not (self == other) |
|
145 |
|
146 def __repr__(self): |
|
147 return "SettingRefsOverride(refs=%r)" % self.refs |
|
148 |
|
149 class CommonImplmlData(object): |
|
150 """ |
|
151 Class representing the common ImplML namespace data read from |
|
152 an XML element. |
|
153 """ |
|
154 |
|
155 def __init__(self): |
|
156 self.phase = None |
|
157 self.tags = None |
|
158 self.tempvar_defs = [] |
|
159 self.setting_refs_override = None |
|
160 self.output_root_dir = None |
|
161 self.output_sub_dir = None |
|
162 |
|
163 def apply(self, impl): |
|
164 """ |
|
165 Apply the data on the given implementation instance. |
|
166 """ |
|
167 if self.phase: |
|
168 impl.set_invocation_phase(self.phase) |
|
169 if self.tags: |
|
170 impl.set_tags(self.tags) |
|
171 if self.setting_refs_override: |
|
172 # Override the get_refs() method of the implementation |
|
173 impl.get_refs = self.setting_refs_override.get_refs |
|
174 # Override also the has_ref() method in case it is overridden |
|
175 # in the implementation sub-class |
|
176 impl.has_ref = lambda refs: plugin.ImplBase.has_ref(impl, refs) |
|
177 if self.output_root_dir: |
|
178 impl.set_output_root_override(self.output_root_dir) |
|
179 if self.output_sub_dir: |
|
180 impl.output_subdir = self.output_sub_dir |
|
181 |
|
182 def extend(self, other): |
|
183 """ |
|
184 Extend this object with the contents of another CommonImplmlData object. |
|
185 """ |
|
186 if other.phase: |
|
187 self.phase = other.phase |
|
188 if other.tags: |
|
189 self.tags = other.tags |
|
190 self.tempvar_defs.extend(other.tempvar_defs) |
|
191 if other.setting_refs_override: |
|
192 self.setting_refs_override = other.setting_refs_override |
|
193 if other.output_root_dir: |
|
194 self.output_root_dir = other.output_root_dir |
|
195 if other.output_sub_dir: |
|
196 self.output_sub_dir = other.output_sub_dir |
|
197 |
|
198 def copy(self): |
|
199 result = CommonImplmlData() |
|
200 result.phase = self.phase |
|
201 if result.tags is not None: |
|
202 result.tags = self.tags.copy() |
|
203 result.tempvar_defs = list(self.tempvar_defs) |
|
204 result.setting_refs_override = copy.deepcopy(self.setting_refs_override) |
|
205 result.output_root_dir = self.output_root_dir |
|
206 result.output_sub_dir = self.output_sub_dir |
|
207 return result |
|
208 |
|
209 def __eq__(self, other): |
|
210 if type(self) is type(other): |
|
211 for varname in ('phase', 'tags', 'tempvar_defs', 'setting_refs_override', 'output_root_dir', 'output_sub_dir'): |
|
212 if getattr(self, varname) != getattr(other, varname): |
|
213 return False |
|
214 return True |
|
215 else: |
|
216 return False |
|
217 |
|
218 def __ne__(self, other): |
|
219 return not (self == other) |
|
220 |
|
221 def __repr__(self): |
|
222 return "CommonImplmlData(phase=%r, tags=%r, tempvar_defs=%r, setting_refs_override=%r, output_root_dir=%r, output_sub_dir=%r)" \ |
|
223 % (self.phase, |
|
224 self.tags, |
|
225 self.tempvar_defs, |
|
226 self.setting_refs_override, |
|
227 self.output_root_dir, |
|
228 self.output_sub_dir) |
|
229 |
|
230 class ImplReader(object): |
|
231 """ |
|
232 Internal reader class for reading implementations from a file in a configuration. |
|
233 """ |
|
234 |
|
235 # The reader class list loaded using ImplFactory |
|
236 __loaded_reader_classes = None |
|
237 __reader_classes = None |
|
238 __supported_file_extensions = None |
|
239 __ignored_namespaces = None |
|
240 |
|
241 def __init__(self, resource_ref, configuration): |
|
242 self.resource_ref = resource_ref |
|
243 self.configuration = configuration |
|
244 |
|
245 @classmethod |
|
246 def _load_data_from_plugins(cls): |
|
247 """ |
|
248 Load all data needed for implementation parsing from the plug-ins. |
|
249 |
|
250 The actual loading is only done the first time this method is called. |
|
251 """ |
|
252 # Load the data only if the reader class list has not been loaded |
|
253 # yet or it has changed |
|
254 loaded_reader_classes = plugin.ImplFactory.get_reader_classes() |
|
255 if cls.__loaded_reader_classes is loaded_reader_classes: |
|
256 return |
|
257 |
|
258 reader_classes = [plugin.ReaderBase] |
|
259 reader_classes.extend(loaded_reader_classes) |
|
260 |
|
261 cls.__reader_classes = {} |
|
262 cls.__ignored_namespaces = [] |
|
263 cls.__supported_file_extensions = [] |
|
264 |
|
265 for rc in reader_classes: |
|
266 # Reader class |
|
267 ns = rc.NAMESPACE |
|
268 if ns is not None: |
|
269 if ns in cls.__reader_classes: |
|
270 raise RuntimeError("Multiple reader classes registered for ImplML namespace '%s': at least %s and %s"\ |
|
271 % (ns, rc, cls.__reader_classes[ns])) |
|
272 cls.__reader_classes[ns] = rc |
|
273 |
|
274 # Ignored namespaces |
|
275 for ns in rc.IGNORED_NAMESPACES: |
|
276 if ns not in cls.__ignored_namespaces: |
|
277 cls.__ignored_namespaces.append(ns) |
|
278 |
|
279 # Supported file extensions |
|
280 for fe in rc.FILE_EXTENSIONS: |
|
281 fe = fe.lower() |
|
282 if fe not in cls.__supported_file_extensions: |
|
283 cls.__supported_file_extensions.append(fe) |
|
284 |
|
285 cls.__loaded_reader_classes = loaded_reader_classes |
|
286 |
|
287 @classmethod |
|
288 def _get_namespaces(cls, etree): |
|
289 """ |
|
290 Return a list of XML namespaces in the given element tree. |
|
291 """ |
|
292 namespaces = [] |
|
293 namespaces.append(utils.xml.split_tag_namespace(etree.tag)[0]) |
|
294 for elem in etree: |
|
295 ns = utils.xml.split_tag_namespace(elem.tag)[0] |
|
296 if ns not in namespaces: |
|
297 namespaces.append(ns) |
|
298 return filter(lambda ns: ns is not None, namespaces) |
|
299 |
|
300 def _read_impls_from_file_root_element(self, root, namespaces): |
|
301 impls = [] |
|
302 reader_classes = self.get_reader_classes() |
|
303 |
|
304 # Go through the list of XML namespaces encountered in the |
|
305 # file and read an implementation using the corresponding |
|
306 # reader for each namespace |
|
307 impl_count = 0 |
|
308 common_data = CommonImplmlDataReader.read_data(root) |
|
309 for ns in namespaces: |
|
310 if ns not in reader_classes: continue |
|
311 |
|
312 rc = reader_classes[ns] |
|
313 impl = self._read_impl(rc, root) |
|
314 if impl: |
|
315 impl.index = impl_count |
|
316 impl_count += 1 |
|
317 if common_data: common_data.apply(impl) |
|
318 impls.append(impl) |
|
319 |
|
320 # Add temp feature definitions to the first implementation |
|
321 if common_data and impls: |
|
322 impls[0]._tempvar_defs.extend(common_data.tempvar_defs) |
|
323 return impls |
|
324 |
|
325 def _read_impls_from_file_sub_elements(self, root): |
|
326 impls = [] |
|
327 |
|
328 # Collect common ImplML namespace data |
|
329 common_data = CommonImplmlData() |
|
330 for elem in root: |
|
331 ns = utils.xml.split_tag_namespace(elem.tag)[0] |
|
332 if ns == COMMON_IMPLML_NAMESPACE: |
|
333 cd = CommonImplmlDataReader.read_data(elem) |
|
334 if cd: common_data.extend(cd) |
|
335 |
|
336 # Go through all sub-elements and read an implementation instance |
|
337 # from each if possible |
|
338 impl_count = 0 |
|
339 reader_classes = self.get_reader_classes() |
|
340 for elem in root: |
|
341 ns = utils.xml.split_tag_namespace(elem.tag)[0] |
|
342 if ns != COMMON_IMPLML_NAMESPACE and ns in reader_classes: |
|
343 reader_class = reader_classes[ns] |
|
344 impl = self._read_impl(reader_class, elem) |
|
345 if impl: |
|
346 cd = CommonImplmlDataReader.read_data(elem) |
|
347 if cd is not None: |
|
348 impl._tempvar_defs.extend(cd.tempvar_defs) |
|
349 data = common_data.copy() |
|
350 data.extend(cd) |
|
351 data.apply(impl) |
|
352 else: |
|
353 common_data.apply(impl) |
|
354 |
|
355 impl.index = impl_count |
|
356 impl_count += 1 |
|
357 impls.append(impl) |
|
358 |
|
359 # Add temporary feature definitions to the first implementation instance |
|
360 if impls: |
|
361 impls[0]._tempvar_defs = common_data.tempvar_defs + impls[0]._tempvar_defs |
|
362 |
|
363 return impls |
|
364 |
|
365 def _read_impl(self, reader_class, elem): |
|
366 """ |
|
367 Read an implementation with the given reader class from the given element. |
|
368 |
|
369 If an exception is raised during reading, the exception is logged |
|
370 and None returned. |
|
371 |
|
372 @return: The read implementation or None. |
|
373 """ |
|
374 try: |
|
375 return reader_class.read_impl(self.resource_ref, self.configuration, elem) |
|
376 except exceptions.ParseError, e: |
|
377 log.error("Error reading implementation '%s': %s", (self.resource_ref, e)) |
|
378 except Exception, e: |
|
379 utils.log_exception(log, e) |
|
380 |
|
381 return None |
|
382 |
|
383 @classmethod |
|
384 def get_reader_classes(cls): |
|
385 """ |
|
386 Return a dictionary of all possible implementation reader classes. |
|
387 |
|
388 Dictionary key is the XML namespace and the value is the corresponding |
|
389 reader class. |
|
390 """ |
|
391 cls._load_data_from_plugins() |
|
392 return cls.__reader_classes |
|
393 |
|
394 @classmethod |
|
395 def get_supported_file_extensions(cls): |
|
396 """ |
|
397 Return a list of all supported implementation file extensions. |
|
398 """ |
|
399 cls._load_data_from_plugins() |
|
400 return cls.__supported_file_extensions |
|
401 |
|
402 @classmethod |
|
403 def get_ignored_namespaces(cls): |
|
404 """ |
|
405 Return a list of all ignored XML namespaces. |
|
406 """ |
|
407 cls._load_data_from_plugins() |
|
408 return cls.__ignored_namespaces |
|
409 |
|
410 def read_implementations(self): |
|
411 try: |
|
412 root = plugin.ReaderBase._read_xml_doc_from_resource(self.resource_ref, self.configuration) |
|
413 return self.read_implementation(root) |
|
414 except exceptions.ParseError, e: |
|
415 # Invalid XML data in the file |
|
416 log.error(e) |
|
417 return [] |
|
418 |
|
419 def read_implementation(self, xmlroot): |
|
420 root = xmlroot |
|
421 |
|
422 # Check if the implementations should all be read from the |
|
423 # document root, or each from its own sub-element under the root |
|
424 read_from_root = False |
|
425 ns = utils.xml.split_tag_namespace(root.tag)[0] |
|
426 if ns: read_from_root = True |
|
427 |
|
428 # Collect namespaces from the file and check that all are supported or ignored |
|
429 namespaces = self._get_namespaces(root) |
|
430 for ns in namespaces: |
|
431 if ns != COMMON_IMPLML_NAMESPACE \ |
|
432 and ns not in self.get_reader_classes() \ |
|
433 and ns not in self.get_ignored_namespaces(): |
|
434 log.error("Unsupported XML namespace '%s' in file '%s'" % (ns, self.resource_ref)) |
|
435 return [] |
|
436 |
|
437 if read_from_root: |
|
438 impls = self._read_impls_from_file_root_element(root, namespaces) |
|
439 else: |
|
440 impls = self._read_impls_from_file_sub_elements(root) |
|
441 return impls |
|
442 |
|
443 |
|
444 class CommonImplmlDataReader(object): |
|
445 """ |
|
446 Internal reader class for reading common ImplML namespace data from and element. |
|
447 """ |
|
448 |
|
449 VALID_PHASES = ('pre', 'normal', 'post') |
|
450 VALID_TYPES = ('string', 'int', 'real', 'boolean') |
|
451 |
|
452 @classmethod |
|
453 def read_data(cls, etree): |
|
454 """ |
|
455 Read common ImplML data from the given XML element. |
|
456 @return: A CommonImplmlData instance or None if no common namespace |
|
457 elements were found. |
|
458 """ |
|
459 result = CommonImplmlData() |
|
460 |
|
461 reader_methods = {'phase' : cls._read_phase, |
|
462 'tag' : cls._read_tag, |
|
463 'tempVariable' : cls._read_tempvar, |
|
464 'tempVariableSequence' : cls._read_tempvarseq, |
|
465 'settingRefsOverride' : cls._read_setting_refs_override, |
|
466 'outputRootDir' : cls._read_output_root_dir, |
|
467 'outputSubDir' : cls._read_output_sub_dir} |
|
468 |
|
469 found = False |
|
470 for elem in etree: |
|
471 ns, tag = utils.xml.split_tag_namespace(elem.tag) |
|
472 if ns != COMMON_IMPLML_NAMESPACE: continue |
|
473 if tag not in reader_methods: continue |
|
474 |
|
475 reader_methods[tag](elem, result) |
|
476 found = True |
|
477 |
|
478 if found: return result |
|
479 else: return None |
|
480 |
|
481 @classmethod |
|
482 def _read_phase(cls, elem, result): |
|
483 phase = elem.get('name') |
|
484 if phase is None: |
|
485 cls._raise_missing_attr(elem, 'name') |
|
486 if phase not in cls.VALID_PHASES: |
|
487 raise exceptions.ParseError("Invalid invocation phase '%s' defined." % phase) |
|
488 |
|
489 result.phase = phase |
|
490 |
|
491 @classmethod |
|
492 def _read_tag(cls, elem, result): |
|
493 name = elem.get('name') |
|
494 value = elem.get('value') |
|
495 if name is not None: |
|
496 if result.tags is None: result.tags = {} |
|
497 if name not in result.tags: result.tags[name] = [] |
|
498 result.tags[name].append(value) |
|
499 |
|
500 @classmethod |
|
501 def _read_tempvar(cls, elem, result): |
|
502 ref = elem.get('ref') |
|
503 type = elem.get('type', 'string') |
|
504 value = elem.get('value', '') |
|
505 |
|
506 if ref is None: |
|
507 cls._raise_missing_attr(elem, 'ref') |
|
508 if type not in cls.VALID_TYPES: |
|
509 cls._raise_invalid_type(ref, type) |
|
510 |
|
511 result.tempvar_defs.append(TempVariableDefinition(ref, type, value)) |
|
512 |
|
513 @classmethod |
|
514 def _read_tempvarseq(cls, elem, result): |
|
515 ref = elem.get('ref') |
|
516 if ref is None: |
|
517 cls._raise_missing_attr(elem, 'ref') |
|
518 |
|
519 sub_items = [] |
|
520 for sub_elem in elem.findall('{%s}tempVariable' % COMMON_IMPLML_NAMESPACE): |
|
521 sub_ref = sub_elem.get('ref') |
|
522 sub_type = sub_elem.get('type', 'string') |
|
523 |
|
524 if sub_ref is None: |
|
525 cls._raise_missing_attr(sub_elem, 'ref') |
|
526 if sub_type not in cls.VALID_TYPES: |
|
527 cls._raise_invalid_type(sub_ref, sub_type) |
|
528 |
|
529 sub_items.append((sub_ref, sub_type)) |
|
530 |
|
531 if not sub_items: |
|
532 raise exceptions.ParseError("Temporary variable sequence '%s' does not have any sub-items" % ref) |
|
533 |
|
534 result.tempvar_defs.append(TempVariableSequenceDefinition(ref, sub_items)) |
|
535 |
|
536 @classmethod |
|
537 def _read_setting_refs_override(cls, elem, result): |
|
538 if elem.get('refsIrrelevant', 'false').lower() in ('1', 'true'): |
|
539 refs = None |
|
540 else: |
|
541 refs = [] |
|
542 for sub_elem in elem.findall('{%s}settingRef' % COMMON_IMPLML_NAMESPACE): |
|
543 ref = sub_elem.get('value') |
|
544 |
|
545 if ref is None: |
|
546 cls._raise_missing_attr(sub_elem, 'value') |
|
547 |
|
548 refs.append(ref) |
|
549 |
|
550 result.setting_refs_override = SettingRefsOverride(refs) |
|
551 |
|
552 @classmethod |
|
553 def _read_output_root_dir(cls, elem, result): |
|
554 value = elem.get('value') |
|
555 if value: result.output_root_dir = value |
|
556 |
|
557 @classmethod |
|
558 def _read_output_sub_dir(cls, elem, result): |
|
559 value = elem.get('value') |
|
560 if value: result.output_sub_dir = value |
|
561 |
|
562 @classmethod |
|
563 def _raise_missing_attr(cls, elem, attrname): |
|
564 raise exceptions.ParseError("XML element %s does not contain the mandatory '%s' attribute." % (elem.tag, attrname)) |
|
565 |
|
566 @classmethod |
|
567 def _raise_invalid_type(cls, ref, type): |
|
568 raise exceptions.ParseError("Invalid feature type '%s' specified for temporary ConfML feature '%s'." % (type, ref)) |