|
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 # @author Teemu Rytkonen |
|
18 |
|
19 import sys |
|
20 import os |
|
21 import re |
|
22 import logging |
|
23 import sets |
|
24 import inspect |
|
25 import xml.parsers.expat |
|
26 |
|
27 from cone.public import exceptions, utils, api, container, settings, rules |
|
28 import _plugin_reader |
|
29 |
|
30 debug = 0 |
|
31 """ |
|
32 Implementation specific settings can be overriden in the global impl_settings variable |
|
33 """ |
|
34 |
|
35 AUTOCONFIG_CONFML = "autodata.confml" |
|
36 |
|
37 def get_autoconfig(configuration): |
|
38 """ |
|
39 Return the "automatic" configuration for storing temporary run-time ConfML |
|
40 features and values. |
|
41 """ |
|
42 lastconfig = configuration.get_last_configuration() |
|
43 if lastconfig.get_path() != AUTOCONFIG_CONFML: |
|
44 logging.getLogger('cone').debug('Adding autodata configuration %s' % AUTOCONFIG_CONFML) |
|
45 configuration.create_configuration(AUTOCONFIG_CONFML) |
|
46 |
|
47 lastconfig = configuration.get_last_configuration() |
|
48 assert lastconfig.get_path() == AUTOCONFIG_CONFML |
|
49 return lastconfig |
|
50 |
|
51 def get_supported_file_extensions(): |
|
52 """ |
|
53 Return a list of all supported ImplML file extension. |
|
54 |
|
55 Implementations are only attempted to be read from files with these |
|
56 extensions. |
|
57 """ |
|
58 return ImplFactory.get_supported_file_extensions() |
|
59 |
|
60 def get_supported_namespaces(): |
|
61 """ |
|
62 Returns a list of all supported ImplML namespaces. |
|
63 """ |
|
64 return ImplFactory.get_reader_dict().keys() |
|
65 |
|
66 def is_temp_feature(feature): |
|
67 """ |
|
68 Return whether the given feature is a temporary feature. |
|
69 """ |
|
70 return hasattr(feature, _plugin_reader.TEMP_FEATURE_MARKER_VARNAME) |
|
71 |
|
72 class GenerationContext(object): |
|
73 """ |
|
74 Context object that can be used for passing generation-scope |
|
75 data to implementation instances. |
|
76 """ |
|
77 |
|
78 def __init__(self, tags={}): |
|
79 #: The tags used in this generation context |
|
80 #: (i.e. the tags passed from command line) |
|
81 self.tags = tags |
|
82 |
|
83 #: The tags policy used in this generation context |
|
84 self.tags_policy = "OR" |
|
85 |
|
86 #: A dictionary that implementation instances can use to |
|
87 #: pass any data between each other |
|
88 self.impl_data_dict = {} |
|
89 |
|
90 #: A string for the phase of the generation |
|
91 self.phase = "" |
|
92 |
|
93 #: a list of rule results |
|
94 self.results = [] |
|
95 |
|
96 #: a pointer to the configuration |
|
97 self.configuration = None |
|
98 |
|
99 def eval(self, ast, expression, value): |
|
100 """ |
|
101 eval for rule evaluation against the context |
|
102 """ |
|
103 pass |
|
104 |
|
105 def handle_terminal(self, expression): |
|
106 """ |
|
107 Handle a terminal object |
|
108 """ |
|
109 try: |
|
110 if isinstance(expression, str): |
|
111 m = re.match("\${(.*)}", expression) |
|
112 if m: |
|
113 try: |
|
114 dview = self.configuration.get_default_view() |
|
115 return dview.get_feature(m.group(1)).value |
|
116 except Exception, e: |
|
117 logging.getLogger('cone').error("Could not dereference feature %s. Exception %s" % (expression, e)) |
|
118 raise e |
|
119 elif expression in ['true','1','True']: |
|
120 return True |
|
121 elif expression in ['false','0','False']: |
|
122 return False |
|
123 else: |
|
124 try: |
|
125 return eval(expression) |
|
126 except NameError: |
|
127 # If the expression is a string in it self it can be returned |
|
128 return expression |
|
129 else: |
|
130 return expression |
|
131 except Exception,e: |
|
132 logging.getLogger('cone').error("Exception with expression %s: %s" % (expression, e)) |
|
133 raise e |
|
134 |
|
135 |
|
136 class FlatComparisonResultEntry(object): |
|
137 """ |
|
138 Class representing a result entry for a flat implementation |
|
139 comparison. |
|
140 |
|
141 Contains the following members: |
|
142 Member Description |
|
143 file Implementation file |
|
144 impl_type Implementation type (e.g. 'crml', 'gcfml') |
|
145 id Entry ID (e.g. CRML repository UID) |
|
146 sub_id Entry sub-ID if applicable (e.g. CRML key UID) |
|
147 value_id Implementation-specific value identifier |
|
148 source_value Value in the source implementation |
|
149 target_value Value in the target implementation |
|
150 |
|
151 data Any extra data (implementation-specific) |
|
152 """ |
|
153 |
|
154 VARNAMES = ['file', 'impl_type', 'id', 'sub_id', 'value_id', 'source_value', 'target_value'] |
|
155 |
|
156 def __init__(self, **kwargs): |
|
157 for varname in self.VARNAMES: |
|
158 setattr(self, varname, kwargs.get(varname)) |
|
159 self.data = kwargs.get('data') |
|
160 |
|
161 def __repr__(self): |
|
162 var_entries = [] |
|
163 for varname in self.VARNAMES: |
|
164 var_entries.append('%s=%r' % (varname, getattr(self, varname))) |
|
165 return "FlatComparisonResultEntry(%s)" % ', '.join(var_entries) |
|
166 |
|
167 def __eq__(self, other): |
|
168 if type(self) is not type(other): |
|
169 return False |
|
170 for varname in self.VARNAMES: |
|
171 if getattr(self, varname) != getattr(other, varname): |
|
172 return False |
|
173 return True |
|
174 |
|
175 def __ne__(self, other): |
|
176 return not (self == other) |
|
177 |
|
178 def __lt__(self, other): |
|
179 for varname in self.VARNAMES: |
|
180 self_val = getattr(self, varname) |
|
181 other_val = getattr(other, varname) |
|
182 if self_val < other_val: return True |
|
183 elif self_val == other_val: pass |
|
184 else: return False |
|
185 return False |
|
186 |
|
187 class DuplicateImplementationEntry(object): |
|
188 """ |
|
189 Class representing an entry of duplicate implementation instances |
|
190 found in a comparison. |
|
191 """ |
|
192 VARNAMES = ['impl_type', 'id', 'files_in_source', 'files_in_target'] |
|
193 |
|
194 def __init__(self, **kwargs): |
|
195 self.impl_type = kwargs.get('impl_type') |
|
196 self.id = kwargs.get('id') |
|
197 self.files_in_source = kwargs.get('files_in_source', []) |
|
198 self.files_in_target = kwargs.get('files_in_target', []) |
|
199 |
|
200 def __repr__(self): |
|
201 var_entries = [] |
|
202 for varname in self.VARNAMES: |
|
203 val = getattr(self, varname) |
|
204 if isinstance(val, list): val = sorted(val) |
|
205 var_entries.append('%s=%r' % (varname, val)) |
|
206 return "DuplicateImplementationEntry(%s)" % ', '.join(var_entries) |
|
207 |
|
208 def __eq__(self, other): |
|
209 if type(self) is not type(other): |
|
210 return False |
|
211 return self.impl_type == other.impl_type \ |
|
212 and self.impl_type == other.impl_type \ |
|
213 and sorted(self.files_in_source) == sorted(other.files_in_source) \ |
|
214 and sorted(self.files_in_target) == sorted(other.files_in_target) |
|
215 |
|
216 def __ne__(self, other): |
|
217 return not (self == other) |
|
218 |
|
219 def __lt__(self, other): |
|
220 for varname in self.VARNAMES: |
|
221 self_val = getattr(self, varname) |
|
222 other_val = getattr(other, varname) |
|
223 if isinstance(self_val, list): self_val = sorted(self_val) |
|
224 if isinstance(other_val, list): other_val = sorted(other_val) |
|
225 if self_val < other_val: return True |
|
226 elif self_val == other_val: pass |
|
227 else: return False |
|
228 return False |
|
229 |
|
230 class FlatComparisonResult(object): |
|
231 """ |
|
232 Class representing a flat comparison result. |
|
233 |
|
234 Each member is a list of FlatComparisonResultEntry |
|
235 objects, except for 'duplicate', which contains |
|
236 DuplicateImplementationEntry objects. |
|
237 |
|
238 Note that the entry members 'value_id', 'source_value' and |
|
239 'target_value' are irrelevant in the 'only_in_source' and |
|
240 'only_in_target' lists, and will always be None. |
|
241 """ |
|
242 def __init__(self, **kwargs): |
|
243 self.only_in_source = kwargs.get('only_in_source', []) |
|
244 self.only_in_target = kwargs.get('only_in_target', []) |
|
245 self.modified = kwargs.get('modified', []) |
|
246 self.duplicate = kwargs.get('duplicate', []) |
|
247 |
|
248 |
|
249 def extend(self, other): |
|
250 """ |
|
251 Extend this comparison result with another one. |
|
252 """ |
|
253 if not isinstance(other, FlatComparisonResult): |
|
254 raise ValueError("Expected instance of %s" % FlatComparisonResult.__name__) |
|
255 |
|
256 self.only_in_source.extend(other.only_in_source) |
|
257 self.only_in_target.extend(other.only_in_target) |
|
258 self.modified.extend(other.modified) |
|
259 |
|
260 def __repr__(self): |
|
261 data = ["FlatComparisonResult(\n"] |
|
262 |
|
263 def get_list_data(lst): |
|
264 if len(lst) == 0: return '[]' |
|
265 |
|
266 temp = ['[\n'] |
|
267 for item in sorted(lst): |
|
268 temp.append(" %r\n" % item) |
|
269 temp.append(' ]') |
|
270 return ''.join(temp) |
|
271 |
|
272 entries = [] |
|
273 for varname in ('only_in_source', 'only_in_target', 'modified', 'duplicate'): |
|
274 entry_text = ' %s = %s' % (varname, get_list_data(getattr(self, varname))) |
|
275 entries.append(entry_text) |
|
276 data.append(',\n'.join(entries)) |
|
277 |
|
278 data.append('\n)') |
|
279 return ''.join(data) |
|
280 |
|
281 def __len__(self): |
|
282 return len(self.only_in_source) + len(self.only_in_target) + len(self.modified) |
|
283 |
|
284 def __eq__(self, other): |
|
285 if type(self) is not type(other): |
|
286 return False |
|
287 return sorted(self.only_in_source) == sorted(other.only_in_source) \ |
|
288 and sorted(self.only_in_target) == sorted(other.only_in_target) \ |
|
289 and sorted(self.modified) == sorted(other.modified) \ |
|
290 and sorted(self.duplicate) == sorted(other.duplicate) |
|
291 |
|
292 def __ne__(self, other): |
|
293 return not (self == other) |
|
294 |
|
295 class ImplBase(object): |
|
296 """ |
|
297 Base class for any implementation class. |
|
298 """ |
|
299 |
|
300 #: Identifier for the implementation type, used e.g. in .cfg files. |
|
301 #: Should be a string like e.g. 'someml'. |
|
302 IMPL_TYPE_ID = None |
|
303 |
|
304 #: Defines the default invocation phase for the implementation. |
|
305 #: The default is used if the phase is not explicitly set in the |
|
306 #: ImplML file or manually overridden by calling set_invocation_phase() |
|
307 DEFAULT_INVOCATION_PHASE = None |
|
308 |
|
309 def __init__(self, ref, configuration): |
|
310 """ |
|
311 Create a ImplBase object |
|
312 @param ref : the ref to the Implml file resource. |
|
313 @param configuration : the Configuration instance for the |
|
314 configuration data. |
|
315 """ |
|
316 self._settings = None |
|
317 self.ref = ref |
|
318 self.index = None |
|
319 self.configuration = configuration |
|
320 self._output_root = self.settings.get('output_root','output') |
|
321 self.output_subdir = self.settings.get('output_subdir','') |
|
322 self.plugin_output = self.settings.get('plugin_output','') |
|
323 |
|
324 self.generation_context = None |
|
325 self._tags = None |
|
326 self._invocation_phase = None |
|
327 self._tempvar_defs = [] |
|
328 self.condition = None |
|
329 self._output_root_override = None |
|
330 |
|
331 def _eval_context(self, context): |
|
332 """ |
|
333 This is a internal function that returns True when the context matches to the |
|
334 context of this implementation. For example phase, tags, etc are evaluated. |
|
335 """ |
|
336 if context.tags and not self.has_tag(context.tags, context.tags_policy): |
|
337 return False |
|
338 if context.phase and not context.phase in self.invocation_phase(): |
|
339 return False |
|
340 if self.condition and not self.condition.eval(context): |
|
341 return False |
|
342 |
|
343 return True |
|
344 |
|
345 def _dereference(self, ref): |
|
346 """ |
|
347 Function for dereferencing a configuration ref to a value in the Implementation configuration context. |
|
348 """ |
|
349 return configuration.get_default_view().get_feature(ref).value |
|
350 |
|
351 def _compare(self, other, dict_keys=None): |
|
352 """ |
|
353 The plugin instance against another plugin instance |
|
354 """ |
|
355 raise exceptions.NotSupportedException() |
|
356 |
|
357 def generate(self, context=None): |
|
358 """ |
|
359 Generate the given implementation. |
|
360 @param context: The generation context can be given as a parameter. |
|
361 The context can contain generation specific parameters for the |
|
362 implementation object itself or the implementation can store data to it |
|
363 which is visible to other implementations. |
|
364 @return: |
|
365 """ |
|
366 raise exceptions.NotSupportedException() |
|
367 |
|
368 def post_generate(self, context=None): |
|
369 """ |
|
370 Called when all normal generation has been done. |
|
371 |
|
372 @param context: The generation context can be given as a parameter. |
|
373 The context can contain generation specific parameters for the |
|
374 implementation object itself or the implementation can store data to it |
|
375 which is visible to other implementations. |
|
376 @attention: This is a temporary method used for implementing cenrep_rfs.txt generation. |
|
377 """ |
|
378 pass |
|
379 |
|
380 def list_output_files(self): |
|
381 """ |
|
382 Return a list of output files as an array. |
|
383 """ |
|
384 return [] |
|
385 |
|
386 def get_refs(self): |
|
387 """ |
|
388 Return a list of all ConfML setting references that affect this |
|
389 implementation. May also return None if references are not relevant |
|
390 for the implementation. |
|
391 """ |
|
392 return None |
|
393 |
|
394 def has_ref(self, refs): |
|
395 """ |
|
396 @param refs: a list of references to check against. |
|
397 @returns True if the implementation uses the given refs as input value, return False if the ref is not found. |
|
398 If refs are not relevant for the given plugin returns None. |
|
399 """ |
|
400 impl_refs = self.get_refs() |
|
401 if impl_refs is None: |
|
402 return None |
|
403 |
|
404 if isinstance(refs, basestring): |
|
405 refs = [refs] |
|
406 |
|
407 for ref in refs: |
|
408 for impl_ref in impl_refs: |
|
409 if ref.startswith(impl_ref): |
|
410 if len(ref) == len(impl_ref): |
|
411 return True |
|
412 elif ref[len(impl_ref)] == '.': |
|
413 return True |
|
414 return False |
|
415 |
|
416 def flat_compare(self, other): |
|
417 """ |
|
418 Return a flat comparison result for two implementations. |
|
419 @param other: The target implementation to compare against. |
|
420 @return: A FlatComparisonResult object. |
|
421 |
|
422 @raise exceptions.NotSupportedException(): The implementation class does not support |
|
423 flat comparison. |
|
424 """ |
|
425 raise exceptions.NotSupportedException() |
|
426 |
|
427 def get_flat_comparison_id(self): |
|
428 """ |
|
429 Return the ID used to uniquely identify this implementation instance for flat comparison. |
|
430 |
|
431 @raise exceptions.NotSupportedException() if the implementation class does not support |
|
432 flat comparison. |
|
433 """ |
|
434 raise exceptions.NotSupportedException() |
|
435 |
|
436 def get_flat_comparison_extra_data(self): |
|
437 """ |
|
438 Return the extra data object for a flat comparison entry. |
|
439 |
|
440 This method is called when an implementation container comparison finds an |
|
441 implementation instance that is not in the other container. |
|
442 |
|
443 @raise exceptions.NotSupportedException() if the implementation class does not support |
|
444 flat comparison. |
|
445 """ |
|
446 raise exceptions.NotSupportedException() |
|
447 |
|
448 @classmethod |
|
449 def get_flat_comparison_impl_type_id(cls): |
|
450 """ |
|
451 Return the type ID used to uniquely identify the current implementation class in flat comparison. |
|
452 |
|
453 @raise exceptions.NotSupportedException() if the implementation class does not support |
|
454 flat comparison. |
|
455 """ |
|
456 raise exceptions.NotSupportedException() |
|
457 |
|
458 @property |
|
459 def settings(self): |
|
460 if not self._settings: |
|
461 parser = settings.SettingsFactory.cone_parser() |
|
462 if self.IMPL_TYPE_ID is not None: |
|
463 section = self.IMPL_TYPE_ID.upper() |
|
464 else: |
|
465 section = settings.DEFAULT_SECTION |
|
466 self._settings = settings.ConeSettings(parser, section) |
|
467 return self._settings |
|
468 |
|
469 @property |
|
470 def output(self): |
|
471 vars = {'output_root': self.output_root,'output_subdir': self.output_subdir,'plugin_output': self.plugin_output} |
|
472 default_format = '%(output_root)s/%(output_subdir)s/%(plugin_output)s' |
|
473 return utils.resourceref.norm(self.settings.get('output',default_format,vars)) |
|
474 |
|
475 def _get_output_root(self): |
|
476 if self._output_root_override is not None: |
|
477 return self._output_root_override |
|
478 else: |
|
479 return self._output_root |
|
480 |
|
481 def _set_output_root(self, value): |
|
482 self._output_root = value |
|
483 |
|
484 output_root = property(_get_output_root, _set_output_root, None, |
|
485 """ |
|
486 The output root directory. |
|
487 |
|
488 Note that if set_output_root_override() has been called with a value |
|
489 other than None, reading this property will always return that value. |
|
490 Otherwise it works just like any other property. |
|
491 """) |
|
492 |
|
493 def get_tags(self): |
|
494 if self._tags is not None: |
|
495 tags = self._tags |
|
496 else: |
|
497 tags = eval(self.settings.get('plugin_tags','{}')) |
|
498 |
|
499 # If we have a configuration, expand setting references in the tags |
|
500 if self.configuration is not None: |
|
501 dview = self.configuration.get_default_view() |
|
502 expanded_tags = {} |
|
503 for name, values in tags.iteritems(): |
|
504 exp_name = utils.expand_refs_by_default_view(name, dview) |
|
505 exp_values = [] |
|
506 expanded_tags[exp_name] = exp_values |
|
507 for value in values: |
|
508 exp_value = utils.expand_refs_by_default_view(value, dview) |
|
509 exp_values.append(exp_value) |
|
510 return expanded_tags |
|
511 else: |
|
512 return tags.copy() |
|
513 |
|
514 |
|
515 def set_tags(self, tags): |
|
516 """ |
|
517 Override the default implementation tags. |
|
518 @param phase: The tag dictionary to set. If None, the implementation's |
|
519 default tags will be used. |
|
520 """ |
|
521 self._tags = tags |
|
522 |
|
523 def has_tag(self, tags, policy=None): |
|
524 """ |
|
525 @param tags: a dictionary of context : tags to check agains |
|
526 @returns True if the implementation has a matching tag. |
|
527 Otherwise return False. |
|
528 """ |
|
529 if (tags==None or len(tags)==0) and len(self.get_tags()) == 0: |
|
530 return True |
|
531 if (tags!=None and len(tags)!=0) and len(self.get_tags()) == 0: |
|
532 return False |
|
533 |
|
534 items = tags.iteritems() |
|
535 self_tags = self.get_tags() |
|
536 if policy == 'AND': |
|
537 for (key,values) in items: |
|
538 tagvals = self_tags.get(key, []) |
|
539 for val in values: |
|
540 if val not in tagvals: |
|
541 return False |
|
542 return True |
|
543 else: |
|
544 for (key,values) in items: |
|
545 tagvals = self_tags.get(key, []) |
|
546 for val in values: |
|
547 if val in tagvals: |
|
548 return True |
|
549 return False |
|
550 |
|
551 return False |
|
552 |
|
553 def set_output_root(self,output): |
|
554 """ |
|
555 Set the root directory for the output files. The output |
|
556 @param output : path to output dir. |
|
557 """ |
|
558 self.output_root = output |
|
559 |
|
560 def get_output_root(self): |
|
561 """ |
|
562 Return the current output dir. |
|
563 """ |
|
564 return self.output_root |
|
565 |
|
566 def set_output_root_override(self, output): |
|
567 """ |
|
568 Set the output root override. |
|
569 @param output: The override value. If None, the normal output root |
|
570 value is used. |
|
571 """ |
|
572 self._output_root_override = output |
|
573 |
|
574 def invocation_phase(self): |
|
575 """ |
|
576 @return: the phase name in which the plugin wants to be executed. |
|
577 """ |
|
578 # 1. Check if overridden on implementation instance level |
|
579 if self._invocation_phase is not None: |
|
580 return self._invocation_phase |
|
581 # 2. Check if overridden on implementation class level |
|
582 elif self.DEFAULT_INVOCATION_PHASE is not None: |
|
583 return self.DEFAULT_INVOCATION_PHASE |
|
584 # 3. Get from settings (if all else fails fall back to 'normal' |
|
585 else: |
|
586 return self.settings.get('plugin_phase', 'normal') |
|
587 |
|
588 def set_invocation_phase(self, phase): |
|
589 """ |
|
590 Override the default invocation phase. |
|
591 @param phase: The invocation phase to set. If None, the implementation's |
|
592 default phase will be used. |
|
593 """ |
|
594 self._invocation_phase = phase |
|
595 |
|
596 def compare(self): |
|
597 """ |
|
598 @return: the phase name in which the plugin wants to be executed. |
|
599 """ |
|
600 return self.settings.get('plugin_phase','normal') |
|
601 |
|
602 def get_temp_variable_definitions(self): |
|
603 return self._tempvar_defs |
|
604 |
|
605 def get_relation_container(self): |
|
606 """ |
|
607 Return a relation container containing all relations from this |
|
608 implementation instance, or None. |
|
609 """ |
|
610 return None |
|
611 |
|
612 def get_all_implementations(self): |
|
613 """ |
|
614 return a list of all actual implementation which is for ImplBase object self. |
|
615 """ |
|
616 return [self] |
|
617 |
|
618 def __repr__(self): |
|
619 return "%s(ref=%r, type=%r, index=%r)" % (self.__class__.__name__, self.ref, self.IMPL_TYPE_ID, self.index) |
|
620 |
|
621 |
|
622 class ImplContainer(ImplBase): |
|
623 """ |
|
624 Acts as a container object with list functionality. |
|
625 """ |
|
626 def __init__(self, ref, configuration): |
|
627 ImplBase.__init__(self, ref, configuration) |
|
628 self.impls = [] |
|
629 |
|
630 # The list functions |
|
631 def __getattr__(self, name): |
|
632 if hasattr(self.impls, name): |
|
633 return self.impls.__getattribute__(name) |
|
634 |
|
635 def __getitem__(self, key): |
|
636 return self.impls[key] |
|
637 |
|
638 def __setitem__(self, key, value): |
|
639 self.impls[key] = value |
|
640 |
|
641 def __delitem__(self, key): |
|
642 del self.impls[key] |
|
643 |
|
644 def __len__(self): |
|
645 return len(self.impls) |
|
646 |
|
647 def __iter__(self): |
|
648 return iter(self.impls) |
|
649 |
|
650 def generate(self, context=None): |
|
651 """ |
|
652 Generate function for container executes generate for all sub implementations. |
|
653 @param context: The generation context can be given as a parameter. The container |
|
654 passes the context to its sub implementations. |
|
655 |
|
656 @return: |
|
657 """ |
|
658 if context: |
|
659 if not self._eval_context(context): |
|
660 # should we report something if we exit here? |
|
661 return |
|
662 |
|
663 # run generate on sub impls |
|
664 for impl in self.impls: |
|
665 impl.generate(context) |
|
666 |
|
667 def get_refs(self): |
|
668 """ |
|
669 Return a list of all ConfML setting references that affect this |
|
670 implementation. May also return None if references are not relevant |
|
671 for the implementation. |
|
672 """ |
|
673 refs = [] |
|
674 for impl in self.impls: |
|
675 subrefs = impl.get_refs() |
|
676 if subrefs: |
|
677 refs += subrefs |
|
678 if refs: |
|
679 return utils.distinct_array(refs) |
|
680 else: |
|
681 return None |
|
682 |
|
683 def get_tags(self): |
|
684 """ |
|
685 overloading the get_tags function in ImplContainer to create sum of |
|
686 tags of all subelements of the Container |
|
687 @return: dictionary of tags |
|
688 """ |
|
689 tags = ImplBase.get_tags(self) |
|
690 for impl in self.impls: |
|
691 # Update the dict by appending new elements to the values instead |
|
692 # of overriding |
|
693 for key,value in impl.get_tags().iteritems(): |
|
694 tags[key] = tags.get(key,[]) + value |
|
695 return tags |
|
696 |
|
697 def list_output_files(self): |
|
698 """ |
|
699 Return a list of output files as an array. |
|
700 """ |
|
701 files = [] |
|
702 for impl in self.impls: |
|
703 files += impl.list_output_files() |
|
704 return utils.distinct_array(files) |
|
705 |
|
706 def set_output_root(self,output): |
|
707 """ |
|
708 Set the root directory for the output files. The output |
|
709 @param output : path to output dir. |
|
710 """ |
|
711 self.output_root = output |
|
712 for impl in self.impls: |
|
713 impl.set_output_root(output) |
|
714 |
|
715 def invocation_phase(self): |
|
716 """ |
|
717 @return: the list of phase names in which phases this container wants to be executed. |
|
718 """ |
|
719 # use a dictionary to store phases only once |
|
720 phases = {} |
|
721 phases[ImplBase.invocation_phase(self)] = 1 |
|
722 for impl in self.impls: |
|
723 # for now only get the phases from sub ImplContainer objects |
|
724 # this is needed until the plugin phase can be overridden with the common elems |
|
725 if isinstance(impl, ImplContainer): |
|
726 subphases = impl.invocation_phase() |
|
727 if isinstance(subphases, list): |
|
728 # join the two lists as one |
|
729 phases = phases.fromkeys(phases.keys() + subphases, 1) |
|
730 else: |
|
731 phases[subphases] = 1 |
|
732 return phases.keys() |
|
733 |
|
734 def get_temp_variable_definitions(self): |
|
735 tempvars = self._tempvar_defs[:] |
|
736 for impl in self.impls: |
|
737 tempvars += impl.get_temp_variable_definitions() |
|
738 return tempvars |
|
739 |
|
740 def get_relation_container(self): |
|
741 """ |
|
742 Return a relation container containing all relations from this |
|
743 container object instance, or empty relation container. |
|
744 """ |
|
745 container = RelationContainer([], '<root>') |
|
746 for impl in self.impls: |
|
747 c = impl.get_relation_container() |
|
748 if isinstance(c, RelationContainer): |
|
749 container.entries.append(c) |
|
750 return container |
|
751 |
|
752 def get_all_implementations(self): |
|
753 """ |
|
754 return a list of all actual implementation under this container |
|
755 """ |
|
756 actual_impls = [] |
|
757 for subimpl in self.impls: |
|
758 actual_impls += subimpl.get_all_implementations() |
|
759 return actual_impls |
|
760 |
|
761 |
|
762 class ReaderBase(object): |
|
763 """ |
|
764 Base class for implementation readers. |
|
765 |
|
766 Each reader class supports one XML namespace, from which it reads an implementation |
|
767 instance. |
|
768 |
|
769 The method for parsing an implementation (read_impl()) is given an ElementTree |
|
770 XML element as the root from which to parse the implementation. The plug-in |
|
771 machinery handles each XML file so that the correct reader class is used to read |
|
772 the implementations from XML elements based on the namespaces. |
|
773 """ |
|
774 |
|
775 #: The XML namespace supported by the implementation reader. |
|
776 #: Should be something like "http://www.xyz.org/xml/1". |
|
777 #: Can also be None, in which case the reader will not be used |
|
778 #: (this can be useful for defining base classes for e.g. readers |
|
779 #: for different versions of an implementation). |
|
780 NAMESPACE = None |
|
781 |
|
782 #: Any extra XML namespaces that should be ignored by the |
|
783 #: implementation parsing machinery. This is useful for specifying |
|
784 #: namespaces that are not actual ImplML namespaces, but are used |
|
785 #: inside an implementation (e.g. XInclude) |
|
786 IGNORED_NAMESPACES = [] |
|
787 |
|
788 #: Supported implementation file extensions. |
|
789 #: Sub-classes can override this to add new supported file extensions |
|
790 #: if necessary. The file extensions simply control whether implementations |
|
791 #: are attempted to be read from a file or not. |
|
792 #: Note that the extensions are case-insensitive. |
|
793 FILE_EXTENSIONS = ['implml'] |
|
794 |
|
795 @classmethod |
|
796 def read_impl(cls, resource_ref, configuration, doc_root): |
|
797 """ |
|
798 Read an implementation instance from the given element tree. |
|
799 |
|
800 @param resource_ref: Reference to the resource in the configuration in |
|
801 which the given document root resides. |
|
802 @param configuration: The configuration used. |
|
803 @param doc_root: The document root from which to parse the implementation. |
|
804 @return: The read implementation instance, or None. |
|
805 """ |
|
806 raise exceptions.NotSupportedException() |
|
807 |
|
808 @classmethod |
|
809 def _read_xml_doc_from_resource(cls, resource_ref, configuration): |
|
810 """ |
|
811 Parse an ElementTree instance from the given resource. |
|
812 """ |
|
813 resource = configuration.get_resource(resource_ref) |
|
814 try: |
|
815 try: |
|
816 data = resource.read() |
|
817 return utils.etree.fromstring(data) |
|
818 except exceptions.XmlParseError, e: |
|
819 msg = "Invalid XML in implementation file '%s'. Exception: %s" % (resource_ref, e) |
|
820 raise e |
|
821 finally: |
|
822 resource.close() |
|
823 |
|
824 class ImplContainerReader(ReaderBase): |
|
825 """ |
|
826 Reader class for reading containers inside implementation files. A container |
|
827 is a implementation in it self that can contain a list of actual implementations. |
|
828 """ |
|
829 NAMESPACE = "http://www.symbianfoundation.org/xml/implml/1" |
|
830 |
|
831 |
|
832 # The reader class list loaded using ImplFactory |
|
833 __reader_classes = None |
|
834 __supported_file_extensions = None |
|
835 __ignored_namespaces = None |
|
836 |
|
837 @classmethod |
|
838 def get_reader_classes(cls): |
|
839 """ |
|
840 Return a dictionary of all possible implementation reader classes. |
|
841 |
|
842 Dictionary key is the XML namespace and the value is the corresponding |
|
843 reader class. |
|
844 """ |
|
845 cls.__reader_classes = ImplFactory.get_reader_dict() |
|
846 return cls.__reader_classes |
|
847 |
|
848 @classmethod |
|
849 def read_impl(cls, resource_ref, configuration, doc_root, read_impl_count=None): |
|
850 # The variable read_impl_count is used to keep track of the number of |
|
851 # currently read actual implementations. It is a list so that it can be used |
|
852 # like a pointer, i.e. functions called from here can modify the number |
|
853 # inside it. A more elegant solution is not done here, since this is temporary |
|
854 # and the index variable in implementation instances will be changed to line_number, |
|
855 # which specifies the actual line on which the implementation is specified in the file |
|
856 if read_impl_count is None: read_impl_count = [0] |
|
857 |
|
858 ns, tag = utils.xml.split_tag_namespace(doc_root.tag) |
|
859 if tag != "container": |
|
860 logging.getLogger('cone').error("Error: The root element must be a container in %s" % (ns, resource_ref)) |
|
861 |
|
862 impls = [] |
|
863 reader_classes = cls.get_reader_classes() |
|
864 namespaces = reader_classes.keys() |
|
865 # Read first the root container object with attributes |
|
866 # and then traverse through possible child containers |
|
867 containerobj = ImplContainer(resource_ref, configuration) |
|
868 containerobj.condition = cls.get_condition(doc_root) |
|
869 |
|
870 common_data = _plugin_reader.CommonImplmlDataReader.read_data(doc_root) |
|
871 |
|
872 # traverse through the subelements |
|
873 for elem in doc_root: |
|
874 ns, tag = utils.xml.split_tag_namespace(elem.tag) |
|
875 if ns == cls.NAMESPACE: |
|
876 # Read a sub-container from the common namespace (all other |
|
877 # common namespace elements were handled earlier) |
|
878 if tag == "container": |
|
879 subcontainer = cls.read_impl(resource_ref, configuration, elem, read_impl_count=read_impl_count) |
|
880 containerobj.append(subcontainer) |
|
881 subcontainer.index = None # For now all sub-containers have index = None |
|
882 else: |
|
883 # Try to read the sub implementation object from some other namespace |
|
884 if ns not in namespaces: |
|
885 logging.getLogger('cone').error("Error: no reader for namespace '%s' in %s" % (ns, resource_ref)) |
|
886 else: |
|
887 reader = reader_classes[ns] |
|
888 subelem = reader.read_impl(resource_ref, configuration, elem) |
|
889 if common_data: common_data.apply(subelem) |
|
890 containerobj.append(subelem) |
|
891 subelem.index = read_impl_count[0] |
|
892 read_impl_count[0] = read_impl_count[0] + 1 |
|
893 |
|
894 if common_data: |
|
895 common_data.apply(containerobj) |
|
896 containerobj._tempvar_defs = common_data.tempvar_defs + containerobj._tempvar_defs |
|
897 return containerobj |
|
898 |
|
899 @classmethod |
|
900 def read_implementation(cls, xml_data): |
|
901 """ |
|
902 Read a container implementation from the given xmlroot element. |
|
903 """ |
|
904 root = utils.etree.fromstring(xml_data) |
|
905 return cls.read_impl("", None,root) |
|
906 |
|
907 @classmethod |
|
908 def get_condition(cls, root): |
|
909 if root.get('condition'): |
|
910 left = root.get('condition') |
|
911 right = root.get('value', 'true') |
|
912 return rules.SimpleCondition(left, right) |
|
913 else: |
|
914 return None |
|
915 |
|
916 class ImplSet(sets.Set): |
|
917 """ |
|
918 Implementation set class that can hold a set of ImplBase instances. |
|
919 """ |
|
920 |
|
921 """ |
|
922 The plugin phases is a list of possible phases in which the plugins are executed. |
|
923 Each plugin instance can tell in which phase it needs to be executed. |
|
924 """ |
|
925 INVOCATION_PHASES = ['pre','normal','post'] |
|
926 |
|
927 def __init__(self,implementations=None, generation_context=None): |
|
928 super(ImplSet,self).__init__(implementations) |
|
929 self.output = 'output' |
|
930 if generation_context: |
|
931 self.generation_context = generation_context |
|
932 else: |
|
933 self.generation_context = GenerationContext() |
|
934 |
|
935 def invocation_phases(self): |
|
936 """ |
|
937 @return: A list of possible invocation phases |
|
938 """ |
|
939 return self.INVOCATION_PHASES |
|
940 |
|
941 def list_output_files(self): |
|
942 """ |
|
943 List the output file names from this container. |
|
944 """ |
|
945 filelist = [] |
|
946 for impl in self: |
|
947 files = impl.list_output_files() |
|
948 filelist.extend(files) |
|
949 return utils.distinct_array(filelist) |
|
950 |
|
951 def generate(self, context=None): |
|
952 """ |
|
953 Generate all implementations. |
|
954 @return: |
|
955 """ |
|
956 #for impl in self.impls: |
|
957 # impl.generation_context = self.generation_context |
|
958 if not context: |
|
959 context = self.generation_context |
|
960 self.execute(self, 'generate', context) |
|
961 |
|
962 def post_generate(self, context=None): |
|
963 """ |
|
964 @attention: This is a temporary method used for implementing cenrep_rfs.txt generation. |
|
965 """ |
|
966 if not context: |
|
967 context = self.generation_context |
|
968 self.execute(self, 'post_generate', context) |
|
969 |
|
970 def execute(self, implementations, methodname, *args): |
|
971 """ |
|
972 Internal function for executing a function to a list of implementations. |
|
973 |
|
974 Mutual execution order (for separate implementation instances defined in |
|
975 the same implementation file) is the order the implementations are |
|
976 specified in the file. |
|
977 |
|
978 @param implementations: |
|
979 @param methodname: the name of the function to execute |
|
980 """ |
|
981 # Sort by (file_name, index_in_file) to ensure the correct execution order |
|
982 impls = sorted(implementations, key=lambda impl: (impl.ref, impl.index)) |
|
983 for impl in impls: |
|
984 try: |
|
985 impl.set_output_root(self.output) |
|
986 if hasattr(impl, methodname): |
|
987 _member = getattr(impl, methodname) |
|
988 _member(*args) |
|
989 else: |
|
990 logging.getLogger('cone').error('Impl %r has no method %s' % (impl, methodname)) |
|
991 except Exception, e: |
|
992 utils.log_exception(logging.getLogger('cone'), 'Impl %r raised an exception: %s' % (impl, repr(e))) |
|
993 |
|
994 |
|
995 def add_implementation(self,impl): |
|
996 """ |
|
997 Add a ImplBase object to this ImplBaseContainer. |
|
998 """ |
|
999 self.add(impl) |
|
1000 |
|
1001 def remove_implementation(self,ref): |
|
1002 """ |
|
1003 Remove implementation object by its ref (name of the implml resource). |
|
1004 """ |
|
1005 impls_to_remove = [] |
|
1006 for impl in self: |
|
1007 if impl.ref == ref: |
|
1008 impls_to_remove.append(impl) |
|
1009 |
|
1010 for impl in impls_to_remove: |
|
1011 self.remove(impl) |
|
1012 |
|
1013 def list_implementation(self): |
|
1014 """ |
|
1015 List all implementation in this container. |
|
1016 @return: an array of resource references. |
|
1017 """ |
|
1018 implrefs = [] |
|
1019 for impl in self: |
|
1020 if impl.ref not in implrefs: |
|
1021 implrefs.append(impl.ref) |
|
1022 return implrefs |
|
1023 |
|
1024 def get_implementations_by_file(self, ref): |
|
1025 """ |
|
1026 Return a list of implementations read from the given file. |
|
1027 """ |
|
1028 return filter(lambda impl: impl.ref == ref, self) |
|
1029 |
|
1030 def filter_implementations(self,**kwargs): |
|
1031 """ |
|
1032 Find any implementation with certain parameters. |
|
1033 All arguments are given as dict, so they must be given with name. E.g. copy(phase='normal') |
|
1034 @param phase: name of the phase |
|
1035 @param refs: A list of refs that are filtered with function has_refs |
|
1036 @param tags: A dictionary of tags that are filtered with function has_tags |
|
1037 @return: a new ImplSet object with the filtered items. |
|
1038 """ |
|
1039 impls = [] |
|
1040 """ Create a list of filter functions for each argument """ |
|
1041 filters=[] |
|
1042 filters.append(lambda x: x != None) |
|
1043 if kwargs.get('phase', None) != None: |
|
1044 filters.append(lambda x: kwargs.get('phase') in x.invocation_phase()) |
|
1045 if kwargs.get('refs',None) != None: |
|
1046 # Changed has_ref usage to allow not supporting refs (meaning that non supported wont be filtered with refs) |
|
1047 filters.append(lambda x: x.has_ref(kwargs.get('refs')) == True or x.has_ref(kwargs.get('refs')) == None) |
|
1048 if kwargs.get('tags', None) != None: |
|
1049 filters.append(lambda x: x.has_tag(kwargs.get('tags'),kwargs.get('policy'))) |
|
1050 |
|
1051 """ Go through the implementations and add all to resultset that pass all filters """ |
|
1052 for impl in self: |
|
1053 pass_filters = True |
|
1054 for filter in filters: |
|
1055 if not filter(impl): |
|
1056 pass_filters = False |
|
1057 break |
|
1058 if pass_filters: |
|
1059 impls.append(impl) |
|
1060 return ImplSet(impls) |
|
1061 |
|
1062 def flat_compare(self, other): |
|
1063 """ |
|
1064 Perform a flat comparison between this implementation container and another one. |
|
1065 @return: @return: A FlatComparisonResult object. |
|
1066 """ |
|
1067 # Collect dictionaries of all comparable implementation instances |
|
1068 # --------------------------------------------------------------- |
|
1069 source_impls_by_class, duplicates_in_source = self._get_flat_comparison_impl_by_class_dicts('source') |
|
1070 target_impls_by_class, duplicates_in_target = other._get_flat_comparison_impl_by_class_dicts('target') |
|
1071 |
|
1072 # Collect a list containing all implementation classes |
|
1073 # ---------------------------------------------------- |
|
1074 all_impl_classes = [] |
|
1075 for impl_class in source_impls_by_class.iterkeys(): |
|
1076 if impl_class not in all_impl_classes: |
|
1077 all_impl_classes.append(impl_class) |
|
1078 for impl_class in target_impls_by_class.iterkeys(): |
|
1079 if impl_class not in all_impl_classes: |
|
1080 all_impl_classes.append(impl_class) |
|
1081 |
|
1082 # Perform comparison for all classes |
|
1083 # ---------------------------------- |
|
1084 result = FlatComparisonResult() |
|
1085 for impl_class in all_impl_classes: |
|
1086 src = source_impls_by_class.get(impl_class, {}) |
|
1087 tgt = target_impls_by_class.get(impl_class, {}) |
|
1088 temp_result = self._get_flat_comparison_result(impl_class, src, tgt) |
|
1089 result.extend(temp_result) |
|
1090 |
|
1091 # Add duplicates into the comparison result |
|
1092 # ----------------------------------------- |
|
1093 def get_or_add_dup_entry(impl_type_id, impl_id): |
|
1094 for e in result.duplicate: |
|
1095 if e.impl_type == impl_type_id and e.id == impl_id: |
|
1096 return e |
|
1097 e = DuplicateImplementationEntry(impl_type=impl_type_id, id=impl_id) |
|
1098 result.duplicate.append(e) |
|
1099 return e |
|
1100 |
|
1101 for impl_class, impl_type_id, impl_id, file in duplicates_in_source: |
|
1102 entry = get_or_add_dup_entry(impl_type_id, impl_id) |
|
1103 entry.files_in_source.append(file) |
|
1104 for impl_class, impl_type_id, impl_id, file in duplicates_in_target: |
|
1105 entry = get_or_add_dup_entry(impl_type_id, impl_id) |
|
1106 entry.files_in_target.append(file) |
|
1107 |
|
1108 # Sort the files so that the output is easier to compare in unit tests |
|
1109 for e in result.duplicate: |
|
1110 e.files_in_source.sort() |
|
1111 e.files_in_target.sort() |
|
1112 |
|
1113 return result |
|
1114 |
|
1115 def _get_flat_comparison_impl_by_class_dicts(self, name): |
|
1116 result = {} |
|
1117 duplicates = [] # List of (impl_class, impl_type_id, impl_id, file) tuples |
|
1118 for impl in self: |
|
1119 # See if the implementation is flat comparable |
|
1120 try: |
|
1121 impl_id = impl.get_flat_comparison_id() |
|
1122 except exceptions.NotSupportedException: |
|
1123 continue |
|
1124 |
|
1125 # Get the dictionary where implementations of this type are collected |
|
1126 impl_class = type(impl) |
|
1127 if impl_class not in result: |
|
1128 result[impl_class] = {} |
|
1129 impls_dict = result[impl_class] |
|
1130 |
|
1131 # Add to the dictionary |
|
1132 if impl_id not in impls_dict: |
|
1133 impls_dict[impl_id] = impl |
|
1134 else: |
|
1135 logging.getLogger('cone').warning("Multiple '%s' implementations with ID %r in %s" % (impl.IMPL_TYPE_ID, impl_id, name)) |
|
1136 duplicates.append((impl_class, impl.IMPL_TYPE_ID, impl_id, impl.ref)) |
|
1137 |
|
1138 # Handle duplicates (add new duplicate entries and |
|
1139 # remove from the dictionaries) |
|
1140 new_duplicates = [] |
|
1141 for impl_class, impl_type_id, impl_id, _ in duplicates: |
|
1142 # Get the corresponding dictionary |
|
1143 if impl_class not in result: continue |
|
1144 impls_dict = result[impl_class] |
|
1145 if impl_id not in impls_dict: continue |
|
1146 impl = impls_dict[impl_id] |
|
1147 |
|
1148 # Add a new entry |
|
1149 new_duplicates.append((impl_class, impl.IMPL_TYPE_ID, impl_id, impl.ref)) |
|
1150 |
|
1151 # Remove from the dictionary |
|
1152 del impls_dict[impl_id] |
|
1153 duplicates.extend(new_duplicates) |
|
1154 |
|
1155 return result, duplicates |
|
1156 |
|
1157 def _get_flat_comparison_result(self, impl_class, source_impls_dict, target_impls_dict): |
|
1158 result = FlatComparisonResult() |
|
1159 impl_type_id = impl_class.get_flat_comparison_impl_type_id() |
|
1160 |
|
1161 for impl_id, impl in target_impls_dict.iteritems(): |
|
1162 if impl_id not in source_impls_dict: |
|
1163 result.only_in_target.append(FlatComparisonResultEntry( |
|
1164 file = impl.ref, |
|
1165 impl_type = impl_type_id, |
|
1166 id = impl_id, |
|
1167 data = impl.get_flat_comparison_extra_data())) |
|
1168 |
|
1169 |
|
1170 def fill_in_fields(entries, field_values): |
|
1171 for entry in entries: |
|
1172 for varname, value in field_values.iteritems(): |
|
1173 setattr(entry, varname, value) |
|
1174 |
|
1175 for impl_id, src_impl in source_impls_dict.iteritems(): |
|
1176 if impl_id not in target_impls_dict: |
|
1177 result.only_in_source.append(FlatComparisonResultEntry( |
|
1178 file = src_impl.ref, |
|
1179 impl_type = impl_type_id, |
|
1180 id = impl_id, |
|
1181 data = src_impl.get_flat_comparison_extra_data())) |
|
1182 else: |
|
1183 tgt_impl = target_impls_dict[impl_id] |
|
1184 |
|
1185 temp_result = src_impl.flat_compare(tgt_impl) |
|
1186 field_values = {'file' : tgt_impl.ref, |
|
1187 'impl_type' : impl_type_id, |
|
1188 'id' : impl_id} |
|
1189 fill_in_fields(temp_result.only_in_source, field_values) |
|
1190 fill_in_fields(temp_result.only_in_target, field_values) |
|
1191 fill_in_fields(temp_result.modified, field_values) |
|
1192 result.extend(temp_result) |
|
1193 |
|
1194 return result |
|
1195 |
|
1196 def create_temp_features(self, configuration): |
|
1197 """ |
|
1198 Create all temporary features for the implementations in this container. |
|
1199 |
|
1200 @param configuration: The configuration where the temporary features are |
|
1201 to be created. |
|
1202 @return: A list containing the references of all created temporary features. |
|
1203 |
|
1204 @raise exceptions.AlreadyExists: Any of the temporary features already exists |
|
1205 in the configuration, or there are duplicate temporary features defined. |
|
1206 """ |
|
1207 # ---------------------------------------------------- |
|
1208 # Collect a list of all temporary variable definitions |
|
1209 # and check for duplicates and already existing |
|
1210 # features at the same time |
|
1211 # ---------------------------------------------------- |
|
1212 tempvar_defs = [] |
|
1213 files_by_refs = {} |
|
1214 dview = configuration.get_default_view() |
|
1215 |
|
1216 for impl in self: |
|
1217 for fea_def in impl.get_temp_variable_definitions(): |
|
1218 # Check if already exists |
|
1219 try: |
|
1220 dview.get_feature(fea_def.ref) |
|
1221 raise exceptions.AlreadyExists( |
|
1222 "Temporary variable '%s' defined in file '%s' already exists in the configuration!" \ |
|
1223 % (fea_def.ref, impl.ref)) |
|
1224 except exceptions.NotFound: |
|
1225 pass |
|
1226 |
|
1227 # Add to temporary dictionary for duplicate checking |
|
1228 if fea_def.ref not in files_by_refs: |
|
1229 files_by_refs[fea_def.ref] = [] |
|
1230 files_by_refs[fea_def.ref].append(impl.ref) |
|
1231 |
|
1232 # Add to the list of all temp feature definitions |
|
1233 tempvar_defs.append(fea_def) |
|
1234 |
|
1235 # Check for duplicates |
|
1236 for ref, file_list in files_by_refs.iteritems(): |
|
1237 if len(file_list) > 1: |
|
1238 raise exceptions.AlreadyExists( |
|
1239 "Duplicate temporary variable! '%s' defined in the following files: %r" \ |
|
1240 % (ref, file_list)) |
|
1241 del files_by_refs |
|
1242 |
|
1243 |
|
1244 # ------------------------------ |
|
1245 # Create the temporary variables |
|
1246 # ------------------------------ |
|
1247 refs = [] |
|
1248 if tempvar_defs: |
|
1249 logging.getLogger('cone').debug('Creating %d temporary variable(s)' % len(tempvar_defs)) |
|
1250 autoconfig = get_autoconfig(configuration) |
|
1251 for fea_def in tempvar_defs: |
|
1252 fea_def.create_feature(autoconfig) |
|
1253 refs.append(fea_def.ref) |
|
1254 |
|
1255 # The default view needs to be recreated, or the created |
|
1256 # features will not be visible there |
|
1257 configuration.recreate_default_view() |
|
1258 return refs |
|
1259 |
|
1260 def get_relation_container(self): |
|
1261 """ |
|
1262 Return a relation container containing all rules from this set |
|
1263 of implementation instances. |
|
1264 """ |
|
1265 container = RelationContainer([], '<root>') |
|
1266 for impl in self: |
|
1267 c = impl.get_relation_container() |
|
1268 if isinstance(c, RelationContainer): |
|
1269 container.entries.append(c) |
|
1270 return container |
|
1271 |
|
1272 def get_all_implementations(self): |
|
1273 """ |
|
1274 Return a flattened list of all implementation instances in this set. |
|
1275 |
|
1276 The returned list contains only actual implementation instances, not |
|
1277 ImplContainer objects. |
|
1278 """ |
|
1279 # Get a list of implementation objects sorted by file name |
|
1280 impl_list = list(self) |
|
1281 impl_list.sort(key=lambda impl: impl.ref) |
|
1282 |
|
1283 result = [] |
|
1284 for impl in impl_list: |
|
1285 result += impl.get_all_implementations() |
|
1286 return result |
|
1287 |
|
1288 |
|
1289 class RelationExecutionResult(object): |
|
1290 """ |
|
1291 Class representing a result from relation execution. |
|
1292 """ |
|
1293 def __init__(self, input_refs, affected_refs, source=None, index=None): |
|
1294 """ |
|
1295 @param input_refs: Input references, i.e. the references on the left side of |
|
1296 the relation. |
|
1297 @param affected_refs: Affected references, i.e. the references of the setting |
|
1298 that have been assigned something as a result of the relation execution. |
|
1299 @param source: The source of the relation. Can be e.g. the path to a RuleML file. |
|
1300 @param index: The index (number) of the relation in the source. This could be |
|
1301 e.g. 1 to denote the first rule in a RuleML file. |
|
1302 """ |
|
1303 self.input_refs = input_refs |
|
1304 self.affected_refs = affected_refs |
|
1305 self.source = source |
|
1306 self.index = index |
|
1307 |
|
1308 def __repr__(self): |
|
1309 return "RelationExecutionResult(input_refs=%r, affected_refs=%r, source=%r, index=%r)" \ |
|
1310 % (sorted(self.input_refs), sorted(self.affected_refs), self.source, self.index) |
|
1311 |
|
1312 def __eq__(self, other): |
|
1313 if type(self) is not type(other): |
|
1314 return False |
|
1315 return sorted(self.input_refs) == sorted(other.input_refs) \ |
|
1316 and sorted(self.affected_refs) == sorted(other.affected_refs) \ |
|
1317 and self.source == other.source \ |
|
1318 and self.index == other.index |
|
1319 |
|
1320 def __ne__(self, other): |
|
1321 return not (self == other) |
|
1322 |
|
1323 class RelationContainer(object): |
|
1324 """ |
|
1325 A relation container that may contain relations or other |
|
1326 RelationContainer objects. |
|
1327 """ |
|
1328 def __init__(self, entries=[], source=None): |
|
1329 """ |
|
1330 @param entries: The relations or relation containers to be added. |
|
1331 @param source: The source of the relations in this container. Can be |
|
1332 e.g. the path to a RuleML file. |
|
1333 """ |
|
1334 self.entries = entries |
|
1335 self.source = source |
|
1336 |
|
1337 def execute(self): |
|
1338 """ |
|
1339 Execute all relations inside the container, logging any exceptions thrown |
|
1340 during the execution. |
|
1341 @return: A list of RelationExecutionResult objects. |
|
1342 """ |
|
1343 results = [] |
|
1344 for i, entry in enumerate(self.entries): |
|
1345 if isinstance(entry, rules.RelationBase): |
|
1346 result = self._execute_relation_and_log_error(entry, self.source, i + 1) |
|
1347 if isinstance(RelationExecutionResult): |
|
1348 results.append(result) |
|
1349 elif isinstance(entry, RelationContainer): |
|
1350 results.extend(self._execute_container_and_log_error(entry)) |
|
1351 else: |
|
1352 logging.getLogger('cone').warning("Invalid RelationContainer entry: type=%s, obj=%r" % (type(entry), entry)) |
|
1353 return results |
|
1354 |
|
1355 def _execute_relation_and_log_error(self, relation, source, index): |
|
1356 """ |
|
1357 Execute a relation, logging any exceptions that may be thrown. |
|
1358 @param relation: The relation to execute. |
|
1359 @param source: The source of the rule. |
|
1360 @param index: The index of the rule, can be None if the index is not known. |
|
1361 @return: The return value from the relation execution, or None if an error occurred. |
|
1362 """ |
|
1363 try: |
|
1364 return relation.execute() |
|
1365 except Exception, e: |
|
1366 log = logging.getLogger('cone') |
|
1367 if index is not None: |
|
1368 utils.log_exception(log, "Error executing rule no. %s in '%s'" % (index, source)) |
|
1369 else: |
|
1370 utils.log_exception(log, "Error executing a rule in '%s'" % relation_or_container.source) |
|
1371 return None |
|
1372 |
|
1373 def _execute_container_and_log_error(self, container): |
|
1374 """ |
|
1375 Execute a relation container, logging any exceptions that may be thrown. |
|
1376 @param relation: The relation container to execute. |
|
1377 @return: The results from the relation execution, or an empty list if an error occurred. |
|
1378 """ |
|
1379 try: |
|
1380 return container.execute() |
|
1381 except Exception, e: |
|
1382 log = logging.getLogger('cone') |
|
1383 utils.log_exception(log, "Error executing rules in '%s'" % container.source) |
|
1384 return [] |
|
1385 |
|
1386 def get_relation_count(self): |
|
1387 """ |
|
1388 Return the number of relations in this container. |
|
1389 """ |
|
1390 count = 0 |
|
1391 for entry in self.entries: |
|
1392 if isinstance(entry, RelationContainer): |
|
1393 count += entry.get_relation_count() |
|
1394 else: |
|
1395 count += 1 |
|
1396 return count |
|
1397 |
|
1398 |
|
1399 class ImplFactory(api.FactoryBase): |
|
1400 |
|
1401 __registered_reader_classes = None |
|
1402 __registered_reader_classes_override = None |
|
1403 __common_reader_classes = [ImplContainerReader] |
|
1404 |
|
1405 @classmethod |
|
1406 def get_reader_classes(cls): |
|
1407 """ |
|
1408 return a list of reader classes |
|
1409 """ |
|
1410 reader_classes = cls.__common_reader_classes[:] |
|
1411 # If the reader class list is overridden, return that |
|
1412 if cls.__registered_reader_classes_override is not None: |
|
1413 reader_classes += cls.__registered_reader_classes_override |
|
1414 else: |
|
1415 # Load the classes if not loaded already |
|
1416 if cls.__registered_reader_classes is None: |
|
1417 cls.__registered_reader_classes = cls.__load_reader_classes() |
|
1418 reader_classes += cls.__registered_reader_classes |
|
1419 |
|
1420 return reader_classes |
|
1421 |
|
1422 @classmethod |
|
1423 def get_reader_dict(cls): |
|
1424 """ |
|
1425 return a dictionary of reader classes, where key is the reader namespace |
|
1426 """ |
|
1427 reader_dict = {} |
|
1428 for reader in cls.get_reader_classes(): |
|
1429 reader_dict[reader.NAMESPACE] = reader |
|
1430 return reader_dict |
|
1431 |
|
1432 @classmethod |
|
1433 def get_supported_file_extensions(cls): |
|
1434 """ |
|
1435 return a dictionary of reader classes, where key is the reader namespace |
|
1436 """ |
|
1437 file_extensions = [] |
|
1438 for reader in cls.get_reader_classes(): |
|
1439 for fe in reader.FILE_EXTENSIONS: |
|
1440 file_extensions.append(fe.lower()) |
|
1441 return file_extensions |
|
1442 |
|
1443 @classmethod |
|
1444 def set_reader_classes_override(cls, reader_classes): |
|
1445 """ |
|
1446 Override the list of registered reader classes. |
|
1447 |
|
1448 This method is provided for unit tests. |
|
1449 @param reader_classes: Reader class list to use as override. Pass None to |
|
1450 disable overriding. |
|
1451 """ |
|
1452 cls.__registered_reader_classes_override = reader_classes |
|
1453 |
|
1454 @classmethod |
|
1455 def force_reload_reader_classes(cls): |
|
1456 """ |
|
1457 Force-reload all reader classes. |
|
1458 """ |
|
1459 cls.__registered_reader_classes = cls.__load_reader_classes() |
|
1460 |
|
1461 @classmethod |
|
1462 def __load_reader_classes(cls): |
|
1463 """ |
|
1464 Load all registered ImplML reader classes from plug-ins. |
|
1465 """ |
|
1466 log = logging.getLogger('cone') |
|
1467 log.setLevel(logging.DEBUG) |
|
1468 reader_classes = [] |
|
1469 ENTRY_POINT = 'cone.plugins.implmlreaders' |
|
1470 |
|
1471 import pkg_resources |
|
1472 working_set = pkg_resources.WorkingSet(sys.path) |
|
1473 for entry_point in working_set.iter_entry_points(ENTRY_POINT): |
|
1474 reader_class = entry_point.load() |
|
1475 if not inspect.isclass(reader_class): |
|
1476 log.warn("'%s' entry point '%s' is not a class (%r)" % (ENTRY_POINT, entry_point.name, reader_class)) |
|
1477 elif not issubclass(reader_class, ReaderBase): |
|
1478 log.warn("'%s' entry point '%s' is not a sub-class of cone.plugin.ReaderBase (%r)" % (ENTRY_POINT, entry_point.name, reader_class)) |
|
1479 else: |
|
1480 msg = "Reader class for XML namespace '%s' loaded from egg '%s' entry point '%s'" % (reader_class.NAMESPACE, ENTRY_POINT, entry_point.name) |
|
1481 log.debug(msg) |
|
1482 #print msg |
|
1483 reader_classes.append(reader_class) |
|
1484 |
|
1485 return reader_classes |
|
1486 |
|
1487 @classmethod |
|
1488 def is_supported_impl_file(cls, file_name): |
|
1489 """ |
|
1490 Return whether the given file is a supported implementation file. |
|
1491 """ |
|
1492 ext = os.path.splitext(file_name)[1] |
|
1493 if ext is not None: |
|
1494 return ext[1:].lower() in cls.get_supported_file_extensions() |
|
1495 else: |
|
1496 return False |
|
1497 |
|
1498 @classmethod |
|
1499 def get_impls_from_file(cls, resource_ref, configuration): |
|
1500 """ |
|
1501 Get a list of implementation instances from the given file (resource in a configuration). |
|
1502 |
|
1503 @param resource_ref: Reference of the resource to read the impls from. |
|
1504 @param configuration: The configuration to use. |
|
1505 @return: List of implementation instances parsed and created from the file. |
|
1506 |
|
1507 @raise NotSupportedException: The file contains an XML namespace that is |
|
1508 not registered as an ImplML namespace. |
|
1509 """ |
|
1510 try: |
|
1511 impls = [] |
|
1512 reader_dict = cls.get_reader_dict() |
|
1513 root = ReaderBase._read_xml_doc_from_resource(resource_ref, configuration) |
|
1514 ns = utils.xml.split_tag_namespace(root.tag)[0] |
|
1515 if ns not in reader_dict.keys(): |
|
1516 logging.getLogger('cone').error("Error: no reader for namespace '%s' in %s" % (ns, resource_ref)) |
|
1517 return [] |
|
1518 rc = reader_dict[ns] |
|
1519 # return the single implementation as a list to maintain |
|
1520 # backwards compability |
|
1521 impl = rc.read_impl(resource_ref, configuration, root) |
|
1522 impl.index = 0 |
|
1523 return [impl] |
|
1524 except exceptions.ParseError, e: |
|
1525 # Invalid XML data in the file |
|
1526 logging.getLogger('cone').error("Implementation %s reading failed with error: %s" % (resource_ref,e)) |
|
1527 return [] |
|
1528 |
|
1529 def get_impl_set(configuration,filter='.*'): |
|
1530 """ |
|
1531 return a ImplSet object that contains all implementation objects related to the |
|
1532 given configuration |
|
1533 """ |
|
1534 impls = configuration.get_layer().list_implml() |
|
1535 impls = pre_filter_impls(impls) |
|
1536 # filter the resources with a given filter |
|
1537 impls = utils.resourceref.filter_resources(impls,filter) |
|
1538 impl_container = create_impl_set(impls,configuration) |
|
1539 return impl_container |
|
1540 |
|
1541 def filtered_impl_set(configuration,pathfilters=None, reffilters=None): |
|
1542 """ |
|
1543 return a ImplSet object that contains all implementation objects related to the |
|
1544 given configuration |
|
1545 """ |
|
1546 if pathfilters: logging.getLogger('cone').info('Filtering impls with %s' % pathfilters) |
|
1547 impls = configuration.get_layer().list_implml() |
|
1548 impls = pre_filter_impls(impls) |
|
1549 # filter the resources with a given filter |
|
1550 if pathfilters: |
|
1551 newimpls = [] |
|
1552 for filter in pathfilters: |
|
1553 newimpls += utils.resourceref.filter_resources(impls,filter) |
|
1554 impls = utils.distinct_array(newimpls) |
|
1555 impl_container = create_impl_set(impls,configuration,reffilters) |
|
1556 return impl_container |
|
1557 |
|
1558 def create_impl_set(impl_filename_list, configuration,reffilters=None): |
|
1559 impl_filename_list = pre_filter_impls(impl_filename_list) |
|
1560 if reffilters: logging.getLogger('cone').info('Filtering with refs %s' % reffilters) |
|
1561 impl_container = ImplSet() |
|
1562 for impl in impl_filename_list: |
|
1563 try: |
|
1564 if configuration != None and ImplFactory.is_supported_impl_file(impl): |
|
1565 plugin_impls = ImplFactory.get_impls_from_file(impl, configuration) |
|
1566 for plugin_impl in plugin_impls: |
|
1567 if not reffilters or plugin_impl.has_ref(reffilters): |
|
1568 impl_container.add_implementation(plugin_impl) |
|
1569 except Exception, e: |
|
1570 utils.log_exception(logging.getLogger('cone'), "Creating impl '%s' failed. Exception: %s" % (impl,e)) |
|
1571 continue |
|
1572 return impl_container |
|
1573 |
|
1574 def pre_filter_impls(impls): |
|
1575 """ |
|
1576 Pre-filter implementation file refs so that files and directories |
|
1577 beginning with a dot (e.g. '.svn', '.scripts') are ignored. |
|
1578 """ |
|
1579 filter = r'(/|^|\\)\..*(/|$|\\)' |
|
1580 return utils.resourceref.neg_filter_resources(impls, filter) |