configurationengine/source/plugins/common/ConeRulePlugin/ruleplugin/relations.py
changeset 0 2e8eeb919028
child 3 e7e0ae78773e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/configurationengine/source/plugins/common/ConeRulePlugin/ruleplugin/relations.py	Thu Mar 11 17:04:37 2010 +0200
@@ -0,0 +1,406 @@
+#
+# Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+# All rights reserved.
+# This component and the accompanying materials are made available
+# under the terms of "Eclipse Public License v1.0"
+# which accompanies this distribution, and is available
+# at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description: 
+#
+
+'''
+implementation for ruleml relations.
+'''
+import os
+import StringIO
+import logging
+import operator as ops
+import re
+import sys, traceback
+
+log = logging.getLogger('cone.ruleplugin.relations')
+
+from cone.public import api, rules, utils, exceptions, plugin
+
+class RelationFactory(api.FactoryBase):
+    @ classmethod
+    def get_relation_by_name(cls, relation_name):
+        """
+        Get the class name by file extension.
+        """
+        try:
+            return rules.RELATIONS.get(relation_name)
+        except KeyError:
+            raise exceptions.NotSupportedException("No Relation class found for name %s" % relation_name)
+
+    @ classmethod
+    def get_relations(cls, configuration, relation):
+        try:
+            relations = []
+            (left_expression,relation_name,right_expression) = parse_rule(relation)
+            relation = cls.get_relation_by_name(relation_name)(configuration, left_expression, right_expression)
+            relations.append(relation)
+            propagated_relations = cls.get_relations(configuration, right_expression)
+            if propagated_relations:
+                for relation in propagated_relations:
+                    relations.append(relation)
+            return relations
+        except exceptions.ParseError:
+            return None
+    
+
+class ConfigurationContext(rules.DefaultContext):
+    
+    def __init__(self, data):
+        rules.DefaultContext.__init__(self, data)
+        
+        # A note on the callback variables that follow:
+        # in order to collect rule execution results for the
+        # generation report, a callback system is implemented in
+        # ConfigurationContext.  It all boils down to ConfigureRelation
+        # using configure_expression_result_callback to catch the result
+        # in its execute() method. The ConfigurationContext just works as
+        # a "callback hub" because a reference to the context is available
+        # in all expression evaluations.
+        
+        # Callback called with a plugin.RelationExecutionResult object
+        # when a ConfigureExpression has been evaluated
+        self.configure_expression_result_callback = None
+        
+        # Callback called with the setting reference when a setting is dereferenced
+        # as a terminal expression
+        self.ref_terminal_callback = None
+        
+        # Callback called with the setting reference when a setting is dereferenced
+        # inside an EvalExpression
+        self.ref_eval_callback = None
+        
+        # Callback called with the setting reference when the value of a setting
+        # is set inside a SetExpression
+        self.ref_set_callback = None
+        
+    def handle_terminal(self, expression):
+        try:
+            value = self.data.get_default_view().get_feature(expression).get_value()
+            
+            # Got a valid ref, call the callback
+            if self.ref_terminal_callback:
+                self.ref_terminal_callback(expression)
+            
+            return value
+        except exceptions.NotFound,e:
+            """ return the expression itself if it is not a fearef """
+            #print "handle_terminal constant %s" % (expression)
+            try:
+                return eval(expression)
+            except (NameError,SyntaxError), e:
+                return expression
+
+    def eval(self, ast, expression, value):
+        #print "expression %s = %s" % (expression,value)
+        pass
+        
+class ConfigurationBaseRelation(rules.BaseRelation):
+    def __init__(self, data, left, right):
+        self.context = ConfigurationContext(data)
+        super(ConfigurationBaseRelation, self).__init__(data, left, right)
+
+class RequireRelation(ConfigurationBaseRelation):
+    KEY = 'requires'
+    def __init__(self, data, left, right):
+        super(RequireRelation, self).__init__(data, left, right)
+        self.context = ConfigurationContext(data)
+
+class ConfigureRelation(ConfigurationBaseRelation):
+    KEY = 'configures'
+    def __init__(self, data, left, right):
+        self.context = ConfigurationContext(data)
+        super(ConfigureRelation, self).__init__(data, left, right)
+        
+        # A plugin.RelationExecutionResult object is stored here
+        self._execution_result = None
+        
+    
+    def execute(self):
+        self._execution_result = None
+        exec_results = []
+        
+        # Call BaseRelation.execute() and catch any ConfigureExpression result objects
+        self.context.configure_expression_result_callback = lambda result: exec_results.append(result)
+        result = rules.BaseRelation.execute(self)
+        self.context.configure_expression_result_callback = None
+        
+        if len(exec_results) > 0:
+            # There should be only one ConfigureExpression inside a ConfigureRelation
+            if len(exec_results) > 1:
+                log.warning("Execution of ConfigureRelation returned more than one result, ignoring all except the first")
+            self._execution_result = exec_results[0]
+        
+        return result
+    
+    def get_execution_result(self):
+        """
+        Return the execution result from the most recent call to execute().
+        """
+        return self._execution_result
+
+def handle_configure(self, left, right):
+    if left and right:
+        return True
+    elif not left:
+        return True
+    return False
+
+def handle_set(self, left, right):
+    left.set_value(right)
+
+def handle_filenamejoin(self, left, right):
+    def extract_dirname(path):
+        """Extract directory name (will always contain a trailing slash or backslash)"""
+        pos = max(path.rfind('/'), path.rfind('\\'))
+        if pos == -1:   return path + '/'
+        else:           return path[:pos + 1]
+    
+    def extract_filename(path):
+        pos = max(path.rfind('/'), path.rfind('\\'))
+        if pos == -1:   return path
+        else:           return path[pos + 1:]
+    
+    return extract_dirname(left) + extract_filename(right)
+
+def handle_plus(self, left, right):
+    return left + right
+
+def handle_minus(self, left, right):
+    return left - right
+
+def handle_multiply(self, left, right):
+    return left * right
+
+def handle_divide(self, left, right):
+    return left / right
+
+class ConfigureExpression(rules.TwoOperatorExpression):
+    PRECEDENCE = rules.PRECEDENCES['RELATION_OPERATORS']
+    KEY = 'configures'
+    OP = handle_configure
+
+    def eval(self, context):
+        input_refs = []
+        affected_refs = []
+        
+        # Evaluate the left-hand side expression, catching refs for the result
+        try:
+            context.ref_terminal_callback = lambda ref: input_refs.append(ref)
+            context.ref_eval_callback = lambda ref: input_refs.append(ref)
+            evaluated_left = self.left.eval(context)
+        finally:
+            context.ref_terminal_callback = None
+            context.ref_eval_callback = None
+        
+        if evaluated_left:
+            # If left evaluated to True, evaluate the right-hand side and
+            # catch refs from SetExpression evaluations
+            try:
+                context.ref_set_callback = lambda ref: affected_refs.append(ref)
+                self.value = self.right.eval(context)
+            finally:
+                context.ref_set_callback = None
+        else:
+            self.value = True
+        
+        if not self.value:
+            left_keys = []
+            for ref in self.ast.extract_refs(str(self.left)):
+                for key in context.get_keys(ref):
+                    left_keys.append(key)
+
+            for key in left_keys:
+                self.ast.add_error(key, { 'error_string' : 'CONFIGURES right side value is "False"',
+                                          'left_key' : key,
+                                          'rule' : self.ast.expression
+                                          })
+        
+        # Return a RelationExecutionResult if necessary
+        if input_refs or affected_refs:
+            if context.configure_expression_result_callback:
+                result = plugin.RelationExecutionResult(utils.distinct_array(input_refs),
+                                                        utils.distinct_array(affected_refs))
+                context.configure_expression_result_callback(result)
+                
+        return self.value
+
+class MultiplyExpression(rules.TwoOperatorExpression):
+    expression = "multiply_operation"
+    PRECEDENCE = rules.PRECEDENCES['MULDIV_OPERATORS']
+    KEY= '*'
+    OP = handle_multiply
+
+class DivideExpression(rules.TwoOperatorExpression):
+    expression = "divide_operation"
+    PRECEDENCE = rules.PRECEDENCES['MULDIV_OPERATORS']
+    KEY= '/'
+    OP = handle_divide
+
+class PlusExpression(rules.TwoOperatorExpression):
+    expression = "plus_operation"
+    PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
+    KEY= '+'
+    OP = handle_plus
+
+class MinusExpression(rules.TwoOperatorExpression):
+    expression = "minus_operation"
+    PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
+    KEY= '-'
+    OP = handle_minus
+
+class EvalExpression(rules.OneParamExpression):
+    expression = "__eval__"
+    PRECEDENCE = rules.PRECEDENCES['PREFIX_OPERATORS']
+    KEY = '__eval__'
+    
+    def __init__(self, ast, expression):
+        super(rules.OneParamExpression, self).__init__(ast)
+        self.expression = expression
+        self._str_to_eval = eval(expression.expression)
+        #self.default_view = default_view
+    
+    def extract_refs(self):
+        result = []
+        result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('${', '}')))
+        result.extend(utils.extract_delimited_tokens(self._str_to_eval, delimiters=('@{', '}')))
+        return result
+    
+    def eval(self, context):
+        # Using the configuration to pass the eval globals dictionary to here,
+        # since there isn't any easy way to do this more elegantly
+        globals_and_locals = {}
+        if hasattr(context.data, '_eval_expression_globals_dict'):
+            globals_and_locals = context.data._eval_expression_globals_dict
+        
+        str_to_eval = self._str_to_eval
+        
+        def expand_feature_ref(ref, index):
+            var_name = "__fea_%05d" % index
+            globals_and_locals[var_name] = context.data.get_default_view().get_feature(ref)
+            if context.ref_eval_callback:
+                context.ref_eval_callback(ref)
+            return var_name
+        def expand_value_ref(ref, index):
+            var_name = "__feaval_%05d" % index
+            globals_and_locals[var_name] = context.data.get_default_view().get_feature(ref).get_value()
+            if context.ref_eval_callback:
+                context.ref_eval_callback(ref)
+            return var_name
+        
+        str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_feature_ref, delimiters=('@{', '}'))
+        str_to_eval = utils.expand_delimited_tokens(str_to_eval, expand_value_ref, delimiters=('${', '}'))
+        
+        # Strip leading and trailing whitespace to avoid indentation problems
+        str_to_eval = str_to_eval.strip()
+        
+        ret = None
+        
+        try:
+            ret = eval(str_to_eval, globals_and_locals)
+            return ret
+        except SyntaxError, e:
+            logging.getLogger('cone.ruleml').warning("Invalid syntax in eval: %s" % (str_to_eval) )
+            self.ast.add_error(self.expression, { 'error_string' : 'Invalid syntax in eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression })
+        except Exception, e:
+            logging.getLogger('cone.ruleml').warning("Execution failed for eval: %s %s: %s" % (str_to_eval, type(e), e) )
+            self.ast.add_error(self.expression, { 'error_string' : 'Execution failed for eval', 'str_to_eval' : str_to_eval, 'rule' : self.ast.expression })
+
+rules.OPERATORS[EvalExpression.KEY] = EvalExpression
+
+class FilenamejoinExpression(rules.TwoOperatorExpression):
+    expression = "filenamejoin"
+    PRECEDENCE = rules.PRECEDENCES['ADDSUB_OPERATORS']
+    KEY = 'filenamejoin'
+    OP = handle_filenamejoin
+    
+rules.OPERATORS[FilenamejoinExpression.KEY] = FilenamejoinExpression
+    
+class SetExpression(rules.TwoOperatorExpression):
+    PRECEDENCE = rules.PRECEDENCES['SET_OPERATORS']
+    KEY= '='
+    OP = handle_set
+
+    def eval(self, context):
+        try:
+            variable = context.data.get_default_view().get_feature(self.left.expression)
+            value = self.right.eval(context)
+            variable.set_value(value)
+            logging.getLogger('cone.ruleml').info("Set %r = %r from %r" % (self.left.expression, value, self.right.expression) )
+            if context.ref_set_callback:
+                context.ref_set_callback(self.left.expression)
+            return True
+        except exceptions.NotFound,e:
+            self.ast.add_error(self.left.expression, { 'error_string' : 'Setting value failed, because of %s' % e,
+                               'left_key' : self.left.expression,
+                               'rule' : self.ast.expression})
+            return False
+
+_relations_and_operators_backup = None
+
+def register():
+    """
+    Register the relations and operators to ConE rules.
+    """
+    global _relations_and_operators_backup
+    if _relations_and_operators_backup is None:
+        # Create the backup copies of the dictionaries
+        rels_backup = rules.RELATIONS.copy()
+        ops_backup = rules.OPERATORS.copy()
+        assert rels_backup is not rules.RELATIONS
+        assert ops_backup is not rules.OPERATORS
+        _relations_and_operators_backup = (rels_backup, ops_backup)
+        
+        # Register relations and operators to rules
+        rules.RELATIONS[RequireRelation.KEY] = RequireRelation
+        rules.RELATIONS[ConfigureRelation.KEY] = ConfigureRelation
+        rules.OPERATORS[ConfigureExpression.KEY] = ConfigureExpression
+        rules.OPERATORS[PlusExpression.KEY] = PlusExpression
+        rules.OPERATORS[SetExpression.KEY] = SetExpression
+        rules.OPERATORS[MinusExpression.KEY] = MinusExpression
+        rules.OPERATORS[MultiplyExpression.KEY] = MultiplyExpression
+        rules.OPERATORS[DivideExpression.KEY] = DivideExpression
+
+def unregister():
+    """
+    Undo the changes made by a call to register().
+    """
+    global _relations_and_operators_backup
+    if _relations_and_operators_backup is not None:
+        rules.RELATIONS = _relations_and_operators_backup[0]
+        rules.OPERATORS = _relations_and_operators_backup[1]
+        _relations_and_operators_backup = None
+
+def parse_rule(rulestring):
+    """
+    Divide the given rule string into (left side, relation, right side) components. 
+    @return: Triple (left side, relation, right side)
+    """
+    left_expression = ''
+    relation_name = None
+    right_expression = ''
+    for token in rules.get_tokens(rulestring):
+        if relation_name == None:
+            if token in rules.RELATIONS.keys():
+                relation_name = token
+            else:
+                left_expression += ' ' + token
+        else:
+            right_expression += ' ' + token
+    
+    if relation_name == None:
+        raise exceptions.ParseError('invalid rule definition %s' % rulestring)
+    
+    return (left_expression,relation_name,right_expression)
+