Coverage Report - org.kuali.rice.krad.uif.util.ExpressionUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
ExpressionUtils
0%
0/104
0%
0/124
13.4
 
 1  
 /**
 2  
  * Copyright 2005-2011 The Kuali Foundation
 3  
  *
 4  
  * Licensed under the Educational Community License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  * http://www.opensource.org/licenses/ecl2.php
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 package org.kuali.rice.krad.uif.util;
 17  
 
 18  
 import org.apache.commons.lang.StringUtils;
 19  
 import org.kuali.rice.krad.uif.UifConstants;
 20  
 import org.kuali.rice.krad.uif.UifPropertyPaths;
 21  
 import org.kuali.rice.krad.uif.view.View;
 22  
 import org.kuali.rice.krad.uif.component.BindingInfo;
 23  
 import org.kuali.rice.krad.uif.component.Component;
 24  
 import org.kuali.rice.krad.uif.layout.LayoutManager;
 25  
 
 26  
 import java.util.HashMap;
 27  
 import java.util.List;
 28  
 import java.util.Map;
 29  
 
 30  
 /**
 31  
  * Utility class for UIF expressions
 32  
  *
 33  
  * @author Kuali Rice Team (rice.collab@kuali.org)
 34  
  */
 35  0
 public class ExpressionUtils {
 36  
 
 37  
     /**
 38  
      * @param view
 39  
      * @param object
 40  
      */
 41  
     public static void adjustPropertyExpressions(View view, Object object) {
 42  0
         if (object == null) {
 43  0
             return;
 44  
         }
 45  
 
 46  
         // get the map of property expressions to adjust
 47  0
         Map<String, String> propertyExpressions = new HashMap<String, String>();
 48  0
         if (Component.class.isAssignableFrom(object.getClass())) {
 49  0
             propertyExpressions = ((Component) object).getPropertyExpressions();
 50  0
         } else if (LayoutManager.class.isAssignableFrom(object.getClass())) {
 51  0
             propertyExpressions = ((LayoutManager) object).getPropertyExpressions();
 52  0
         } else if (BindingInfo.class.isAssignableFrom(object.getClass())) {
 53  0
             propertyExpressions = ((BindingInfo) object).getPropertyExpressions();
 54  
         }
 55  
 
 56  0
         boolean defaultPathSet = StringUtils.isNotBlank(view.getDefaultBindingObjectPath());
 57  
 
 58  0
         Map<String, String> adjustedPropertyExpressions = new HashMap<String, String>();
 59  0
         for (Map.Entry<String, String> propertyExpression : propertyExpressions.entrySet()) {
 60  0
             String propertyName = propertyExpression.getKey();
 61  0
             String expression = propertyExpression.getValue();
 62  
 
 63  
             // if property name is nested, need to move the expression to the parent object
 64  0
             if (StringUtils.contains(propertyName, ".")) {
 65  0
                 boolean expressionMoved = moveNestedPropertyExpression(object, propertyName, expression);
 66  
 
 67  
                 // if expression moved, skip rest of control statement so it is not added to the adjusted map
 68  0
                 if (expressionMoved) {
 69  0
                     continue;
 70  
                 }
 71  
             }
 72  
 
 73  0
             adjustedPropertyExpressions.put(propertyName, expression);
 74  0
         }
 75  
 
 76  
         // update property expressions map on object
 77  0
         ObjectPropertyUtils.setPropertyValue(object, UifPropertyPaths.PROPERTY_EXPRESSIONS,
 78  
                 adjustedPropertyExpressions);
 79  
 
 80  
         // TODO: In progress, adjusting paths in expressions
 81  
 //            if (defaultPathSet) {
 82  
 //                String adjustedExpression = "";
 83  
 //
 84  
 //                // check for expression placeholder wrapper or multiple expressions (template)
 85  
 //                if (StringUtils.contains(expression, "@{") && StringUtils.contains(expression, "}")) {
 86  
 //                    String remainder = expression;
 87  
 //
 88  
 //                    while (StringUtils.isNotBlank(remainder) && StringUtils.contains(remainder, "@{") && StringUtils
 89  
 //                            .contains(remainder, "}")) {
 90  
 //                        String beforeLiteral = StringUtils.substringBefore(remainder, "@{");
 91  
 //                        String afterBeginDelimiter = StringUtils.substringAfter(remainder, "@{");
 92  
 //                        String nestedExpression = StringUtils.substringBefore(afterBeginDelimiter, "}");
 93  
 //                        remainder = StringUtils.substringAfter(afterBeginDelimiter, "}");
 94  
 //
 95  
 //                        if (StringUtils.isNotBlank(beforeLiteral)) {
 96  
 //                            adjustedExpression += beforeLiteral;
 97  
 //                        }
 98  
 //                        adjustedExpression += "@{";
 99  
 //
 100  
 //                        if (StringUtils.isNotBlank(nestedExpression)) {
 101  
 //                            String adjustedNestedExpression = processExpression(nestedExpression,
 102  
 //                                    view.getDefaultBindingObjectPath());
 103  
 //                            adjustedExpression += adjustedNestedExpression;
 104  
 //                        }
 105  
 //                        adjustedExpression += "}";
 106  
 //                    }
 107  
 //
 108  
 //                    // add last remainder if was a literal (did not contain expression placeholders)
 109  
 //                    if (StringUtils.isNotBlank(remainder)) {
 110  
 //                        adjustedExpression += remainder;
 111  
 //                    }
 112  
 //                } else {
 113  
 //                    // treat expression as one
 114  
 //                    adjustedExpression = processExpression(expression, view.getDefaultBindingObjectPath());
 115  
 //                }
 116  
 //
 117  
 //                adjustedPropertyExpressions.put(propertyName, adjustedExpression);
 118  
 //            } else {
 119  
 //                adjustedPropertyExpressions.put(propertyName, expression);
 120  
 //            }
 121  
 //        }
 122  
 //
 123  
 
 124  0
     }
 125  
 
 126  
     protected static String processExpression(String expression, String pathPrefix) {
 127  0
         String processedExpression = "";
 128  
 
 129  0
         Tokenizer tokenizer = new Tokenizer(expression);
 130  0
         tokenizer.process();
 131  
 
 132  0
         Tokenizer.Token previousToken = null;
 133  0
         for (Tokenizer.Token token : tokenizer.getTokens()) {
 134  0
             if (token.isIdentifier()) {
 135  
                 // if an identifier, verify it is a model property name (must be at beginning of expression of
 136  
                 // come after a space)
 137  0
                 String identifier = token.stringValue();
 138  0
                 if ((previousToken == null) || (previousToken.isIdentifier() && StringUtils.isBlank(
 139  
                         previousToken.stringValue()))) {
 140  
                     // append path prefix unless specified as form property
 141  0
                     if (!StringUtils.startsWith(identifier, "form")) {
 142  0
                         identifier = pathPrefix + "." + identifier;
 143  
                     }
 144  
                 }
 145  0
                 processedExpression += identifier;
 146  0
             } else if (token.getKind().tokenChars.length != 0) {
 147  0
                 processedExpression += new String(token.getKind().tokenChars);
 148  
             } else {
 149  0
                 processedExpression += token.stringValue();
 150  
             }
 151  
 
 152  0
             previousToken = token;
 153  
         }
 154  
 
 155  
         // remove special binding prefixes
 156  0
         processedExpression = StringUtils.replace(processedExpression, UifConstants.NO_BIND_ADJUST_PREFIX, "");
 157  
 
 158  0
         return processedExpression;
 159  
     }
 160  
 
 161  
     protected static boolean moveNestedPropertyExpression(Object object, String propertyName, String expression) {
 162  0
         boolean moved = false;
 163  
 
 164  
         // get the parent object for the property
 165  0
         String parentPropertyName = StringUtils.substringBeforeLast(propertyName, ".");
 166  0
         String propertyNameInParent = StringUtils.substringAfterLast(propertyName, ".");
 167  
 
 168  0
         Object parentObject = ObjectPropertyUtils.getPropertyValue(object, parentPropertyName);
 169  0
         if ((parentObject != null) && ObjectPropertyUtils.isReadableProperty(parentObject,
 170  
                 UifPropertyPaths.PROPERTY_EXPRESSIONS) && ((parentObject instanceof Component)
 171  
                 || (parentObject instanceof LayoutManager)
 172  
                 || (parentObject instanceof BindingInfo))) {
 173  0
             Map<String, String> propertyExpressions = ObjectPropertyUtils.getPropertyValue(parentObject,
 174  
                     UifPropertyPaths.PROPERTY_EXPRESSIONS);
 175  0
             if (propertyExpressions == null) {
 176  0
                 propertyExpressions = new HashMap<String, String>();
 177  
             }
 178  
 
 179  
             // add expression to map on parent object
 180  0
             propertyExpressions.put(propertyNameInParent, expression);
 181  0
             ObjectPropertyUtils.setPropertyValue(parentObject, UifPropertyPaths.PROPERTY_EXPRESSIONS,
 182  
                     propertyExpressions);
 183  0
             moved = true;
 184  
         }
 185  
 
 186  0
         return moved;
 187  
     }
 188  
 
 189  
     /**
 190  
      * Takes in an expression and a list to be filled in with names(property names)
 191  
      * of controls found in the expression. This method returns a js expression which can
 192  
      * be executed on the client to determine if the original exp was satisfied before
 193  
      * interacting with the server - ie, this js expression is equivalent to the one passed in.
 194  
      *
 195  
      * There are limitations on the Spring expression language that can be used as this method.
 196  
      * It is only used to parse expressions which are valid case statements for determining if
 197  
      * some action/processing should be performed.  ONLY Properties, comparison operators, booleans,
 198  
      * strings, matches expression, and boolean logic are supported.  Properties must
 199  
      * be a valid property on the form, and should have a visible control within the view.
 200  
      *
 201  
      * Example valid exp: account.name == 'Account Name'
 202  
      *
 203  
      * @param exp
 204  
      * @param controlNames
 205  
      * @return
 206  
      */
 207  
     public static String parseExpression(String exp, List<String> controlNames) {
 208  
         // clean up expression to ease parsing
 209  0
         exp = exp.trim();
 210  0
         if(exp.startsWith("@{")){
 211  0
             exp = StringUtils.removeStart(exp, "@{");
 212  0
             if(exp.endsWith("}")){
 213  0
                 exp = StringUtils.removeEnd(exp,"}");
 214  
             }
 215  
         }
 216  
 
 217  0
         exp = StringUtils.replace(exp, "!=", " != ");
 218  0
         exp = StringUtils.replace(exp, "==", " == ");
 219  0
         exp = StringUtils.replace(exp, ">", " > ");
 220  0
         exp = StringUtils.replace(exp, "<", " < ");
 221  0
         exp = StringUtils.replace(exp, "<=", " <= ");
 222  0
         exp = StringUtils.replace(exp, ">=", " >= ");
 223  
 
 224  
 
 225  0
         String conditionJs = exp;
 226  0
         String stack = "";
 227  
 
 228  0
         boolean expectingSingleQuote = false;
 229  0
         boolean ignoreNext = false;
 230  0
         for (int i = 0; i < exp.length(); i++) {
 231  0
             char c = exp.charAt(i);
 232  0
             if (!expectingSingleQuote && !ignoreNext && (c == '(' || c == ' ' || c == ')')) {
 233  0
                 evaluateCurrentStack(stack.trim(), controlNames);
 234  
                 //reset stack
 235  0
                 stack = "";
 236  0
                 continue;
 237  0
             } else if (!ignoreNext && c == '\'') {
 238  0
                 stack = stack + c;
 239  0
                 expectingSingleQuote = !expectingSingleQuote;
 240  0
             } else if (c == '\\') {
 241  0
                 stack = stack + c;
 242  0
                 ignoreNext = !ignoreNext;
 243  
             } else {
 244  0
                 stack = stack + c;
 245  0
                 ignoreNext = false;
 246  
             }
 247  
         }
 248  
 
 249  0
         if(StringUtils.isNotEmpty(stack)){
 250  0
            evaluateCurrentStack(stack.trim(), controlNames);
 251  
         }
 252  
 
 253  0
         conditionJs = conditionJs.replaceAll("\\s(?i:ne)\\s", " != ").replaceAll("\\s(?i:eq)\\s", " == ").replaceAll(
 254  
                 "\\s(?i:gt)\\s", " > ").replaceAll("\\s(?i:lt)\\s", " < ").replaceAll("\\s(?i:lte)\\s", " <= ")
 255  
                 .replaceAll("\\s(?i:gte)\\s", " >= ").replaceAll("\\s(?i:and)\\s", " && ").replaceAll("\\s(?i:or)\\s",
 256  
                         " || ").replaceAll("\\s(?i:not)\\s", " != ").replaceAll("\\s(?i:null)\\s?", " '' ").replaceAll(
 257  
                         "\\s?(?i:#empty)\\((.*?)\\)", "isValueEmpty($1)");
 258  
 
 259  0
         if (conditionJs.contains("matches")) {
 260  0
             conditionJs = conditionJs.replaceAll("\\s+(?i:matches)\\s+'.*'", ".match(/" + "$0" + "/) != null ");
 261  0
             conditionJs = conditionJs.replaceAll("\\(/\\s+(?i:matches)\\s+'", "(/");
 262  0
             conditionJs = conditionJs.replaceAll("'\\s*/\\)", "/)");
 263  
         }
 264  
 
 265  0
         for (String propertyName : controlNames) {
 266  0
             conditionJs = conditionJs.replace(propertyName, "coerceValue(\"" + propertyName + "\")");
 267  
         }
 268  
 
 269  0
         return conditionJs;
 270  
     }
 271  
 
 272  
     /**
 273  
      * Used internally by parseExpression to evalute if the current stack is a property
 274  
      * name (ie, will be a control on the form)
 275  
      *
 276  
      * @param stack
 277  
      * @param controlNames
 278  
      */
 279  
     public static void evaluateCurrentStack(String stack, List<String> controlNames) {
 280  0
         if (StringUtils.isNotBlank(stack)) {
 281  0
             if (!(stack.equals("==")
 282  
                     || stack.equals("!=")
 283  
                     || stack.equals(">")
 284  
                     || stack.equals("<")
 285  
                     || stack.equals(">=")
 286  
                     || stack.equals("<=")
 287  
                     || stack.equalsIgnoreCase("ne")
 288  
                     || stack.equalsIgnoreCase("eq")
 289  
                     || stack.equalsIgnoreCase("gt")
 290  
                     || stack.equalsIgnoreCase("lt")
 291  
                     || stack.equalsIgnoreCase("lte")
 292  
                     || stack.equalsIgnoreCase("gte")
 293  
                     || stack.equalsIgnoreCase("matches")
 294  
                     || stack.equalsIgnoreCase("null")
 295  
                     || stack.equalsIgnoreCase("false")
 296  
                     || stack.equalsIgnoreCase("true")
 297  
                     || stack.equalsIgnoreCase("and")
 298  
                     || stack.equalsIgnoreCase("or")
 299  
                     || stack.contains("#empty")
 300  
                     || stack.startsWith("'")
 301  
                     || stack.endsWith("'"))) {
 302  
 
 303  0
                 boolean isNumber = false;
 304  0
                 if ((StringUtils.isNumeric(stack.substring(0, 1)) || stack.substring(0, 1).equals("-"))) {
 305  
                     try {
 306  0
                         Double.parseDouble(stack);
 307  0
                         isNumber = true;
 308  0
                     } catch (NumberFormatException e) {
 309  0
                         isNumber = false;
 310  0
                     }
 311  
                 }
 312  
 
 313  0
                 if (!(isNumber)) {
 314  0
                     if (!controlNames.contains(stack)) {
 315  0
                         controlNames.add(stack);
 316  
                     }
 317  
                 }
 318  
             }
 319  
         }
 320  0
     }
 321  
 
 322  
 }