configurationengine/source/plugins/common/ConeRulePlugin/ruleplugin/relations.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 '''
       
    18 implementation for ruleml relations.
       
    19 '''
       
    20 import os
       
    21 import StringIO
       
    22 import logging
       
    23 import operator as ops
       
    24 import re
       
    25 import sys, traceback
       
    26 
       
    27 log = logging.getLogger('cone.ruleplugin.relations')
       
    28 
       
    29 from cone.public import api, rules, utils, exceptions, plugin
       
    30 
       
    31 class RelationFactory(api.FactoryBase):
       
    32     @ classmethod
       
    33     def get_relation_by_name(cls, relation_name):
       
    34         """
       
    35         Get the class name by file extension.
       
    36         """
       
    37         try:
       
    38             return rules.RELATIONS.get(relation_name)
       
    39         except KeyError:
       
    40             raise exceptions.NotSupportedException("No Relation class found for name %s" % relation_name)
       
    41 
       
    42     @ classmethod
       
    43     def get_relations(cls, configuration, relation):
       
    44         try:
       
    45             relations = []
       
    46             (left_expression,relation_name,right_expression) = parse_rule(relation)
       
    47             relation = cls.get_relation_by_name(relation_name)(configuration, left_expression, right_expression)
       
    48             relations.append(relation)
       
    49             propagated_relations = cls.get_relations(configuration, right_expression)
       
    50             if propagated_relations:
       
    51                 for relation in propagated_relations:
       
    52                     relations.append(relation)
       
    53             return relations
       
    54         except exceptions.ParseError:
       
    55             return None
       
    56     
       
    57 
       
    58 class ConfigurationContext(rules.DefaultContext):
       
    59     
       
    60     def __init__(self, data):
       
    61         rules.DefaultContext.__init__(self, data)
       
    62         
       
    63         # A note on the callback variables that follow:
       
    64         # in order to collect rule execution results for the
       
    65         # generation report, a callback system is implemented in
       
    66         # ConfigurationContext.  It all boils down to ConfigureRelation
       
    67         # using configure_expression_result_callback to catch the result
       
    68         # in its execute() method. The ConfigurationContext just works as
       
    69         # a "callback hub" because a reference to the context is available
       
    70         # in all expression evaluations.
       
    71         
       
    72         # Callback called with a plugin.RelationExecutionResult object
       
    73         # when a ConfigureExpression has been evaluated
       
    74         self.configure_expression_result_callback = None
       
    75         
       
    76         # Callback called with the setting reference when a setting is dereferenced
       
    77         # as a terminal expression
       
    78         self.ref_terminal_callback = None
       
    79         
       
    80         # Callback called with the setting reference when a setting is dereferenced
       
    81         # inside an EvalExpression
       
    82         self.ref_eval_callback = None
       
    83         
       
    84         # Callback called with the setting reference when the value of a setting
       
    85         # is set inside a SetExpression
       
    86         self.ref_set_callback = None
       
    87         
       
    88     def handle_terminal(self, expression):
       
    89         try:
       
    90             value = self.data.get_default_view().get_feature(expression).get_value()
       
    91             
       
    92             # Got a valid ref, call the callback
       
    93             if self.ref_terminal_callback:
       
    94                 self.ref_terminal_callback(expression)
       
    95             
       
    96             return value
       
    97         except exceptions.NotFound,e:
       
    98             """ return the expression itself if it is not a fearef """
       
    99             #print "handle_terminal constant %s" % (expression)
       
   100             try:
       
   101                 return eval(expression)
       
   102             except (NameError,SyntaxError), e:
       
   103                 return expression
       
   104 
       
   105     def eval(self, ast, expression, value):
       
   106         #print "expression %s = %s" % (expression,value)
       
   107         pass
       
   108         
       
   109 class ConfigurationBaseRelation(rules.BaseRelation):
       
   110     def __init__(self, data, left, right):
       
   111         self.context = ConfigurationContext(data)
       
   112         super(ConfigurationBaseRelation, self).__init__(data, left, right)
       
   113 
       
   114 class RequireRelation(ConfigurationBaseRelation):
       
   115     KEY = 'requires'
       
   116     def __init__(self, data, left, right):
       
   117         super(RequireRelation, self).__init__(data, left, right)
       
   118         self.context = ConfigurationContext(data)
       
   119 
       
   120 class ConfigureRelation(ConfigurationBaseRelation):
       
   121     KEY = 'configures'
       
   122     def __init__(self, data, left, right):
       
   123         self.context = ConfigurationContext(data)
       
   124         super(ConfigureRelation, self).__init__(data, left, right)
       
   125         
       
   126         # A plugin.RelationExecutionResult object is stored here
       
   127         self._execution_result = None
       
   128         
       
   129     
       
   130     def execute(self):
       
   131         self._execution_result = None
       
   132         exec_results = []
       
   133         
       
   134         # Call BaseRelation.execute() and catch any ConfigureExpression result objects
       
   135         self.context.configure_expression_result_callback = lambda result: exec_results.append(result)
       
   136         result = rules.BaseRelation.execute(self)
       
   137         self.context.configure_expression_result_callback = None
       
   138         
       
   139         if len(exec_results) > 0:
       
   140             # There should be only one ConfigureExpression inside a ConfigureRelation
       
   141             if len(exec_results) > 1:
       
   142                 log.warning("Execution of ConfigureRelation returned more than one result, ignoring all except the first")
       
   143             self._execution_result = exec_results[0]
       
   144         
       
   145         return result
       
   146     
       
   147     def get_execution_result(self):
       
   148         """
       
   149         Return the execution result from the most recent call to execute().
       
   150         """
       
   151         return self._execution_result
       
   152 
       
   153 def handle_configure(self, left, right):
       
   154     if left and right:
       
   155         return True
       
   156     elif not left:
       
   157         return True
       
   158     return False
       
   159 
       
   160 def handle_set(self, left, right):
       
   161     left.set_value(right)
       
   162 
       
   163 def handle_filenamejoin(self, left, right):
       
   164     def extract_dirname(path):
       
   165         """Extract directory name (will always contain a trailing slash or backslash)"""
       
   166         pos = max(path.rfind('/'), path.rfind('\\'))
       
   167         if pos == -1:   return path + '/'
       
   168         else:           return path[:pos + 1]
       
   169     
       
   170     def extract_filename(path):
       
   171         pos = max(path.rfind('/'), path.rfind('\\'))
       
   172         if pos == -1:   return path
       
   173         else:           return path[pos + 1:]
       
   174     
       
   175     return extract_dirname(left) + extract_filename(right)
       
   176 
       
   177 def handle_plus(self, left, right):
       
   178     return left + right
       
   179 
       
   180 def handle_minus(self, left, right):
       
   181     return left - right
       
   182 
       
   183 def handle_multiply(self, left, right):
       
   184     return left * right
       
   185 
       
   186 def handle_divide(self, left, right):
       
   187     return left / right
       
   188 
       
   189 class ConfigureExpression(rules.TwoOperatorExpression):
       
   190     PRECEDENCE = rules.PRECEDENCES['RELATION_OPERATORS']
       
   191     KEY = 'configures'
       
   192     OP = handle_configure
       
   193 
       
   194     def eval(self, context):
       
   195         input_refs = []
       
   196         affected_refs = []
       
   197         
       
   198         # Evaluate the left-hand side expression, catching refs for the result
       
   199         try:
       
   200             context.ref_terminal_callback = lambda ref: input_refs.append(ref)
       
   201             context.ref_eval_callback = lambda ref: input_refs.append(ref)
       
   202             evaluated_left = self.left.eval(context)
       
   203         finally:
       
   204             context.ref_terminal_callback = None
       
   205             context.ref_eval_callback = None
       
   206         
       
   207         if evaluated_left:
       
   208             # If left evaluated to True, evaluate the right-hand side and
       
   209             # catch refs from SetExpression evaluations
       
   210             try:
       
   211                 context.ref_set_callback = lambda ref: affected_refs.append(ref)
       
   212                 self.value = self.right.eval(context)
       
   213             finally:
       
   214                 context.ref_set_callback = None
       
   215         else:
       
   216             self.value = True
       
   217         
       
   218         if not self.value:
       
   219             left_keys = []
       
   220             for ref in self.ast.extract_refs(str(self.left)):
       
   221                 for key in context.get_keys(ref):
       
   222                     left_keys.append(key)
       
   223 
       
   224             for key in left_keys:
       
   225                 self.ast.add_error(key, { 'error_string' : 'CONFIGURES right side value is "False"',
       
   226                                           'left_key' : key,
       
   227                                           'rule' : self.ast.expression
       
   228                                           })
       
   229         
       
   230         # Return a RelationExecutionResult if necessary
       
   231         if input_refs or affected_refs:
       
   232             if context.configure_expression_result_callback:
       
   233                 result = plugin.RelationExecutionResult(utils.distinct_array(input_refs),
       
   234                                                         utils.distinct_array(affected_refs))
       
   235                 context.configure_expression_result_callback(result)
       
   236                 
       
   237         return self.value
       
   238 
       
   239 class MultiplyExpression(rules.TwoOperatorExpression):
       
   240     expression = "multiply_operation"
       
   241     PRECEDENCE = rules.PRECEDENCES['MULDIV_OPERATORS']
       
   242     KEY= '*'
       
   243     OP = handle_multiply
       
   244 
       
   245 class DivideExpression(rules.TwoOperatorExpression):
       
   246     expression = "divide_operation"
       
   247     PRECEDENCE = rules.PRECEDENCES['MULDIV_OPERATORS']
       
   248     KEY= '/'
       
   249     OP = handle_divide
       
   250 
       
   251 class PlusExpression(rules.TwoOperatorExpression):
       
   252     expression = "plus_operation"
       
   253     PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
       
   254     KEY= '+'
       
   255     OP = handle_plus
       
   256 
       
   257 class MinusExpression(rules.TwoOperatorExpression):
       
   258     expression = "minus_operation"
       
   259     PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
       
   260     KEY= '-'
       
   261     OP = handle_minus
       
   262 
       
   263 class EvalExpression(rules.OneParamExpression):
       
   264     expression = "__eval__"
       
   265     PRECEDENCE = rules.PRECEDENCES['PREFIX_OPERATORS']
       
   266     KEY = '__eval__'
       
   267     
       
   268     def __init__(self, ast, expression):
       
   269         super(rules.OneParamExpression, self).__init__(ast)
       
   270         self.expression = expression
       
   271         self._str_to_eval = eval(expression.expression)
       
   272         #self.default_view = default_view
       
   273     
       
   274     def extract_refs(self):
       
   275         result = []
       
   276         result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('${', '}')))
       
   277         result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('@{', '}')))
       
   278         return result
       
   279     
       
   280     def eval(self, context):
       
   281         # Using the configuration to pass the eval globals dictionary to here,
       
   282         # since there isn't any easy way to do this more elegantly
       
   283         globals_and_locals = {}
       
   284         if hasattr(context.data, '_eval_expression_globals_dict'):
       
   285             globals_and_locals = context.data._eval_expression_globals_dict
       
   286         
       
   287         str_to_eval = self._str_to_eval
       
   288         
       
   289         def expand_feature_ref(ref, index):
       
   290             var_name = "__fea_%05d" % index
       
   291             globals_and_locals[var_name] = context.data.get_default_view().get_feature(ref)
       
   292             if context.ref_eval_callback:
       
   293                 context.ref_eval_callback(ref)
       
   294             return var_name
       
   295         def expand_value_ref(ref, index):
       
   296             var_name = "__feaval_%05d" % index
       
   297             globals_and_locals[var_name] = context.data.get_default_view().get_feature(ref).get_value()
       
   298             if context.ref_eval_callback:
       
   299                 context.ref_eval_callback(ref)
       
   300             return var_name
       
   301         
       
   302         str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_feature_ref, delimiters=('@{', '}'))
       
   303         str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_value_ref, delimiters=('${', '}'))
       
   304         
       
   305         # Strip leading and trailing whitespace to avoid indentation problems
       
   306         str_to_eval = str_to_eval.strip()
       
   307         
       
   308         ret = None
       
   309         
       
   310         try:
       
   311             ret = eval(str_to_eval, globals_and_locals)
       
   312             return ret
       
   313         except SyntaxError, e:
       
   314             logging.getLogger('cone.ruleml').warning("Invalid syntax in eval: %s" % (str_to_eval) )
       
   315             self.ast.add_error(self.expression, { 'error_string' : 'Invalid syntax in eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression })
       
   316         except Exception, e:
       
   317             logging.getLogger('cone.ruleml').warning("Execution failed for eval: %s %s: %s" % (str_to_eval, type(e), e) )
       
   318             self.ast.add_error(self.expression, { 'error_string' : 'Execution failed for eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression })
       
   319 
       
   320 rules.OPERATORS[EvalExpression.KEY] = EvalExpression
       
   321 
       
   322 class FilenamejoinExpression(rules.TwoOperatorExpression):
       
   323     expression = "filenamejoin"
       
   324     PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
       
   325     KEY = 'filenamejoin'
       
   326     OP = handle_filenamejoin
       
   327     
       
   328 rules.OPERATORS[FilenamejoinExpression.KEY] = FilenamejoinExpression
       
   329     
       
   330 class SetExpression(rules.TwoOperatorExpression):
       
   331     PRECEDENCE = rules.PRECEDENCES['SET_OPERATORS']
       
   332     KEY= '='
       
   333     OP = handle_set
       
   334 
       
   335     def eval(self, context):
       
   336         try:
       
   337             variable = context.data.get_default_view().get_feature(self.left.expression)
       
   338             value = self.right.eval(context)
       
   339             variable.set_value(value)
       
   340             logging.getLogger('cone.ruleml').info("Set %r = %r from %r" % (self.left.expression, value, self.right.expression) )
       
   341             if context.ref_set_callback:
       
   342                 context.ref_set_callback(self.left.expression)
       
   343             return True
       
   344         except exceptions.NotFound,e:
       
   345             self.ast.add_error(self.left.expression, { 'error_string' : 'Setting value failed, because of %s' % e,
       
   346                                'left_key' : self.left.expression,
       
   347                                'rule' : self.ast.expression})
       
   348             return False
       
   349 
       
   350 _relations_and_operators_backup = None
       
   351 
       
   352 def register():
       
   353     """
       
   354     Register the relations and operators to ConE rules.
       
   355     """
       
   356     global _relations_and_operators_backup
       
   357     if _relations_and_operators_backup is None:
       
   358         # Create the backup copies of the dictionaries
       
   359         rels_backup = rules.RELATIONS.copy()
       
   360         ops_backup = rules.OPERATORS.copy()
       
   361         assert rels_backup is not rules.RELATIONS
       
   362         assert ops_backup is not rules.OPERATORS
       
   363         _relations_and_operators_backup = (rels_backup, ops_backup)
       
   364         
       
   365         # Register relations and operators to rules
       
   366         rules.RELATIONS[RequireRelation.KEY] = RequireRelation
       
   367         rules.RELATIONS[ConfigureRelation.KEY] = ConfigureRelation
       
   368         rules.OPERATORS[ConfigureExpression.KEY] = ConfigureExpression
       
   369         rules.OPERATORS[PlusExpression.KEY] = PlusExpression
       
   370         rules.OPERATORS[SetExpression.KEY] = SetExpression
       
   371         rules.OPERATORS[MinusExpression.KEY] = MinusExpression
       
   372         rules.OPERATORS[MultiplyExpression.KEY] = MultiplyExpression
       
   373         rules.OPERATORS[DivideExpression.KEY] = DivideExpression
       
   374 
       
   375 def unregister():
       
   376     """
       
   377     Undo the changes made by a call to register().
       
   378     """
       
   379     global _relations_and_operators_backup
       
   380     if _relations_and_operators_backup is not None:
       
   381         rules.RELATIONS = _relations_and_operators_backup[0]
       
   382         rules.OPERATORS = _relations_and_operators_backup[1]
       
   383         _relations_and_operators_backup = None
       
   384 
       
   385 def parse_rule(rulestring):
       
   386     """
       
   387     Divide the given rule string into (left side, relation, right side) components. 
       
   388     @return: Triple (left side, relation, right side)
       
   389     """
       
   390     left_expression = ''
       
   391     relation_name = None
       
   392     right_expression = ''
       
   393     for token in rules.get_tokens(rulestring):
       
   394         if relation_name == None:
       
   395             if token in rules.RELATIONS.keys():
       
   396                 relation_name = token
       
   397             else:
       
   398                 left_expression += ' ' + token
       
   399         else:
       
   400             right_expression += ' ' + token
       
   401     
       
   402     if relation_name == None:
       
   403         raise exceptions.ParseError('invalid rule definition %s' % rulestring)
       
   404     
       
   405     return (left_expression,relation_name,right_expression)
       
   406