configurationengine/source/cone/public/plugin.py
changeset 0 2e8eeb919028
child 3 e7e0ae78773e
equal deleted inserted replaced
-1:000000000000 0:2e8eeb919028
       
     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)