简体   繁体   中英

Evaluating logical expression which is represented as string in java

I am working on supporting notifications on kindle devices for our app. We will receive the expression sting from server which contains topics (topics will be in single quotes). We need to validate the expression by checking whether device is subscribed to the topics or not. Below is the example how logical expression looks like.

('18' in topics || '4' in topics) && ('REG_92779' in topics || 'REG_91212' in topics)

Assume the device is subscribed to topics '18' and '4' and not subscribed to other topics, the notification is not valid for the device. If the device is subscribed to topics '18' and 'REG_91212', the notification is valid for the device. I wrote the below code and it is working fine.

private boolean checkNotificationValid(String leString) {
        // Remove the 'in topics' string. examples of logical expression as below
        // ('4' in topics && '50000' in topics) || ('60' in topics || '3' in topics)
        // ('4' in topics && ('50000' in topics || '60' in topics))
        leString = leString.replaceAll("in topics", "");
        Log.d(TAG, "Logical Expression received : " + leString);
        boolean result = false;

        // Find the topics in the logical expression and check whether the device is subscribed to
        // the topic. If device is subscribed to the topic, replace the topic with 1 or replace the
        // topic with 0. Below is the example.
        // Assume device is subscribed to topics 3 and 4.
        // Expression : ('4' && '50000') || ('60' || '3')
        // After this block of code, it becomes : ('1' && '0') || ('0' || '1')
        StringBuffer buffer = new StringBuffer();
        Pattern p = Pattern.compile("'(.*?)'");
        Matcher m = p.matcher(leString);
        while(m.find()) {
            Log.d(TAG, "LE : " + m.group(1));
            // Check whether the device is subscribed to the topic
            m.appendReplacement(buffer, NotificationUtils.isTopicExistInSharedPref(m.group(1)) ? "1" : "0");
        }

        m.appendTail(buffer);
        leString = buffer.toString();

        // Remove the quotes and spaces from the string, replace '||' with '|', replace '&&' with '&'
        // ('1' && '0') || ('0' || '1') -> (1&0)|(0|1)
        leString = leString.replaceAll("[' ]", "");
        leString = leString.replaceAll("\\|\\|", "|");
        leString = leString.replaceAll("&&", "&");
        Log.d(TAG, "String after changing : " + leString);

        // Evaluate the logical expression
        result = evaluateTopicExpression(leString);

        return result;
    }

    private boolean evaluateTopicExpression(String expString) {
        int len = expString.length();
        int idx = 0;
        boolean result = false;
        int parenCnt = 0;

        // Check for empty expression
        if(len == 0) {
            Log.d(TAG, "empty expression in evaluateTopicExpression");
            return false;
        }

        // If there is only one character it shouldn't be logical operator and shouldn't be
        // parentheses as we are removing parentheses while evaluating the expression.
        if(len == 1) {
            if(expString.charAt(idx) == '0') {
                return false;
            } else {
                return true;
            }
        }

        // Check if the starting character is '('. If it is open parentheses, find the matching
        // closed parentheses. We need to find the exact closed parentheses, extract the string
        // between the parentheses and evaluate that string, concatenate the value to the remaining
        // string and evaluate the string.
        // Example as below.
        // (1|0)&(1&1) -> evaluate 1|0 -> result of 1|0 is 1 -> concatenate the result to remaining
        //                string -> 1&(1&1) -> evaluate the string
        // (1|0)&1 -> evaluate 1|0 -> result of 1|0 is 1 -> concatenate the result to remaining string
        //            -> 1&1 -> evaluate the string
        // (1&(0|1)) -> evaluate 1&(0|1) (This is because once we remove the last parentheses there
        //              is no remaining string after the parentheses)
        if(expString.charAt(idx) == '(') {
            int endIndex = 0;
            parenCnt ++;
            for(int i = idx + 1; i < len; i++) {
                if(expString.charAt(i) == '(') {
                    parenCnt++;
                } else if(expString.charAt(i) == ')') {
                    parenCnt--;
                }
                if(parenCnt == 0) {
                    endIndex = i;
                    break;
                }
            }

            if(parenCnt != 0) {
                Log.d(TAG, "Invalid matching parentheses");
                return false;
            }

            if(endIndex == len - 1) {
                return evaluateTopicExpression(expString.substring(1, endIndex));
            }
            else {
                return evaluateTopicExpression((evaluateTopicExpression(expString.substring(1, endIndex)) ? '1' : '0')
                        + expString.substring(endIndex + 1));
            }
        }

        // If expression string length >= 2, evaluate the first 2 characters in the string and evaluate
        // the remaining string in the next recursive function call
        if(idx + 2 < len) {
            if(expString.charAt(idx + 1) == '|') {
                if(expString.charAt(idx) == '0') {
                    result = evaluateTopicExpression(expString.substring(idx + 2));
                } else {
                    result =  true;
                }
            }

            if(expString.charAt(idx + 1) == '&') {
                if(expString.charAt(idx) == '1') {
                    result = evaluateTopicExpression(expString.substring(idx + 2));
                } else {
                    result = false;
                }
            }
        } else {
            Log.d(TAG, "Error, Invalid logical expression");
            result = false;
        }

        return result;
    }

checkNotificationValid() function replaces topics with 1 or 0 (based on whether device subscribed / not), trims the spaces, remove the quotes, replace '||' with '|', replace '&&' with '&' ... before sending the shortened string to evaluateTopicExpression().

For example, if the logical expression is

('18' in topics || '4' in topics) && ('REG_92779' in topics || 'REG_91212' in topics)

and the device is subscribed to topics '18', '4', 'REG_92779', checkNotificationValid() calls evaluateTopicExpression() with the below string

(1|1)&(1|0)

I tested with lot of expressions and the code is working fine. Even though code is working fine, I feel like evaluateTopicExpression() has lot of code and it is not efficient code as I am going through each character and calling the function recursively. Is there anyway we can reduce the code and evaluate the expression in more efficient way.

NOTE: Instead of replacing the code with 1 or 0, we can replace with true or false like

(true||true)&&(true||false)

All I need to do is whether the notification is valid for the device or not.

Do the parsing an the evaluation in the same recursive function boolean evalExpression(String expr) . This way you can avoid recursion into redundant trees. If you evaluate one half of a expression that has the && operator and it's false you don't need to evaluate the other half.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM