Coverage Report - org.mule.util.TemplateParser
 
Classes in this File Line Coverage Branch Coverage Complexity
TemplateParser
0%
0/86
0%
0/46
0
TemplateParser$1
0%
0/2
N/A
0
TemplateParser$PatternInfo
0%
0/58
0%
0/46
0
TemplateParser$TemplateCallback
N/A
N/A
0
 
 1  
 /*
 2  
  * $Id: TemplateParser.java 20441 2010-12-02 16:15:09Z dfeist $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 5  
  *
 6  
  * The software in this package is published under the terms of the CPAL v1.0
 7  
  * license, a copy of which has been included with this distribution in the
 8  
  * LICENSE.txt file.
 9  
  */
 10  
 
 11  
 package org.mule.util;
 12  
 
 13  
 import java.util.ArrayList;
 14  
 import java.util.HashMap;
 15  
 import java.util.Iterator;
 16  
 import java.util.List;
 17  
 import java.util.Map;
 18  
 import java.util.Stack;
 19  
 import java.util.regex.Matcher;
 20  
 import java.util.regex.Pattern;
 21  
 
 22  
 import org.apache.commons.logging.Log;
 23  
 import org.apache.commons.logging.LogFactory;
 24  
 
 25  
 /**
 26  
  * <code>TemplateParser</code> is a simple string parser that will substitute
 27  
  * tokens in a string with values supplied in a Map.
 28  
  */
 29  
 public final class TemplateParser
 30  
 {
 31  
     public static final String ANT_TEMPLATE_STYLE = "ant";
 32  
     public static final String SQUARE_TEMPLATE_STYLE = "square";
 33  
     public static final String CURLY_TEMPLATE_STYLE = "curly";
 34  
     public static final String WIGGLY_MULE_TEMPLATE_STYLE = "mule";
 35  
 
 36  
     private static final String DOLLAR_ESCAPE = "@@@";
 37  
 
 38  0
     private static final Map<String, PatternInfo> patterns = new HashMap<String, PatternInfo>();
 39  
 
 40  
     static
 41  
     {
 42  0
         patterns.put(ANT_TEMPLATE_STYLE, new PatternInfo(ANT_TEMPLATE_STYLE, "\\$\\{[^\\}]+\\}", "${", "}"));
 43  0
         patterns.put(SQUARE_TEMPLATE_STYLE, new PatternInfo(SQUARE_TEMPLATE_STYLE, "\\[[^\\]]+\\]", "[", "]"));
 44  0
         patterns.put(CURLY_TEMPLATE_STYLE, new PatternInfo(CURLY_TEMPLATE_STYLE, "\\{[^\\}]+\\}", "{", "}"));
 45  
 
 46  
         // Such a complex regex is needed to support nested expressions, otherwise we
 47  
         // have to do this manually or using an ANTLR grammar etc.
 48  
 
 49  
         // Support for 3 levels (2 nested)
 50  0
         patterns.put(WIGGLY_MULE_TEMPLATE_STYLE, new PatternInfo(WIGGLY_MULE_TEMPLATE_STYLE,
 51  
             "#\\[((?:#\\[(?:#\\[.*?\\]|\\[.*?\\]|.)*?\\]|\\[.*?\\]|.)*?)\\]", "#[", "]"));
 52  
 
 53  
         // Support for 2 levels (1 nested)
 54  
         // "#\\[((?:#\\[.*?\\]|\\[.*?\\]|.)*?)\\]"
 55  
     }
 56  
 
 57  
     /**
 58  
      * logger used by this class
 59  
      */
 60  0
     protected static final Log logger = LogFactory.getLog(TemplateParser.class);
 61  
 
 62  0
     public static final Pattern ANT_TEMPLATE_PATTERN = patterns.get(ANT_TEMPLATE_STYLE).getPattern();
 63  0
     public static final Pattern SQUARE_TEMPLATE_PATTERN = patterns.get(SQUARE_TEMPLATE_STYLE).getPattern();
 64  0
     public static final Pattern CURLY_TEMPLATE_PATTERN = patterns.get(CURLY_TEMPLATE_STYLE).getPattern();
 65  0
     public static final Pattern WIGGLY_MULE_TEMPLATE_PATTERN = patterns.get(WIGGLY_MULE_TEMPLATE_STYLE).getPattern();
 66  
 
 67  
     private final Pattern pattern;
 68  
     private final int pre;
 69  
     private final int post;
 70  
     private final PatternInfo style;
 71  
 
 72  
 
 73  
     public static TemplateParser createAntStyleParser()
 74  
     {
 75  0
         return new TemplateParser(ANT_TEMPLATE_STYLE);
 76  
     }
 77  
 
 78  
     public static TemplateParser createSquareBracesStyleParser()
 79  
     {
 80  0
         return new TemplateParser(SQUARE_TEMPLATE_STYLE);
 81  
     }
 82  
 
 83  
     public static TemplateParser createCurlyBracesStyleParser()
 84  
     {
 85  0
         return new TemplateParser(CURLY_TEMPLATE_STYLE);
 86  
     }
 87  
 
 88  
     public static TemplateParser createMuleStyleParser()
 89  
     {
 90  0
         return new TemplateParser(WIGGLY_MULE_TEMPLATE_STYLE);
 91  
     }
 92  
 
 93  
     private TemplateParser(String styleName)
 94  0
     {
 95  0
         this.style = patterns.get(styleName);
 96  0
         if (this.style == null)
 97  
         {
 98  0
             throw new IllegalArgumentException("Unknown template style: " + styleName);
 99  
 
 100  
         }
 101  0
         pattern = style.getPattern();
 102  0
         pre = style.getPrefix().length();
 103  0
         post = style.getSuffix().length();
 104  0
     }
 105  
 
 106  
     /**
 107  
      * Matches one or more templates against a Map of key value pairs. If a value for
 108  
      * a template is not found in the map the template is left as is in the return
 109  
      * String
 110  
      *
 111  
      * @param props    the key/value pairs to match against
 112  
      * @param template the string containing the template place holders i.e. My name
 113  
      *                 is ${name}
 114  
      * @return the parsed String
 115  
      */
 116  
     public String parse(Map props, String template)
 117  
     {
 118  0
         return parse(props, template, null);
 119  
     }
 120  
 
 121  
     /**
 122  
      * Matches one or more templates against a Map of key value pairs. If a value for
 123  
      * a template is not found in the map the template is left as is in the return
 124  
      * String
 125  
      *
 126  
      * @param callback a callback used to resolve the property name
 127  
      * @param template the string containing the template place holders i.e. My name
 128  
      *                 is ${name}
 129  
      * @return the parsed String
 130  
      */
 131  
     public String parse(TemplateCallback callback, String template)
 132  
     {
 133  0
         return parse(null, template, callback);
 134  
     }
 135  
 
 136  
     protected String parse(Map props, String template, TemplateCallback callback)
 137  
     {
 138  0
         String result = template;
 139  0
         Map newProps = props;
 140  0
         if (props != null && !(props instanceof CaseInsensitiveHashMap))
 141  
         {
 142  0
             newProps = new CaseInsensitiveHashMap(props);
 143  
         }
 144  
 
 145  0
         Matcher m = pattern.matcher(result);
 146  
 
 147  0
         while (m.find())
 148  
         {
 149  0
             Object value = null;
 150  
 
 151  0
             String match = m.group();
 152  0
             String propname = match.substring(pre, match.length() - post);
 153  
 
 154  0
             if (callback != null)
 155  
             {
 156  0
                 value = callback.match(propname);
 157  
             }
 158  0
             else if (newProps != null)
 159  
             {
 160  0
                 value = newProps.get(propname);
 161  
             }
 162  
 
 163  0
             if (value == null)
 164  
             {
 165  0
                 if (logger.isDebugEnabled())
 166  
                 {
 167  0
                     logger.debug("Value " + propname + " not found in context");
 168  
                 }
 169  
             }
 170  
             else
 171  
             {
 172  0
                 String matchRegex = escape(match);
 173  0
                 String valueString = value.toString();
 174  
                 //need to escape $ as they resolve into group references, escaping them was not enough
 175  
                 //This smells a bit like a hack, but one way or another these characters need to be escaped
 176  0
                 if (valueString.indexOf('$') != -1)
 177  
                 {
 178  0
                     valueString = valueString.replaceAll("\\$", DOLLAR_ESCAPE);
 179  
                 }
 180  
 
 181  0
                 if (valueString.indexOf('\\') != -1)
 182  
                 {
 183  0
                     valueString = valueString.replaceAll("\\\\", "\\\\\\\\");
 184  
                 }
 185  
 
 186  0
                 result = result.replaceAll(matchRegex, valueString);
 187  
             }
 188  0
         }
 189  0
         if (result.indexOf(DOLLAR_ESCAPE) != -1)
 190  
         {
 191  0
             result = result.replaceAll(DOLLAR_ESCAPE, "\\$");
 192  
         }
 193  0
         return result;
 194  
     }
 195  
 
 196  
     /**
 197  
      * Matches one or more templates against a Map of key value pairs. If a value for
 198  
      * a template is not found in the map the template is left as is in the return
 199  
      * String
 200  
      *
 201  
      * @param props     the key/value pairs to match against
 202  
      * @param templates A List of templates
 203  
      * @return the parsed String
 204  
      */
 205  
     public List parse(Map props, List templates)
 206  
     {
 207  0
         if (templates == null)
 208  
         {
 209  0
             return new ArrayList();
 210  
         }
 211  0
         List list = new ArrayList(templates.size());
 212  0
         for (Iterator iterator = templates.iterator(); iterator.hasNext();)
 213  
         {
 214  0
             list.add(parse(props, iterator.next().toString()));
 215  
         }
 216  0
         return list;
 217  
     }
 218  
 
 219  
     /**
 220  
      * Matches one or more templates against a Map of key value pairs. If a value for
 221  
      * a template is not found in the map the template is left as is in the return
 222  
      * String
 223  
      *
 224  
      * @param props     the key/value pairs to match against
 225  
      * @param templates A Map of templates. The values for each map entry will be
 226  
      *                  parsed
 227  
      * @return the parsed String
 228  
      */
 229  
     public Map parse(final Map props, Map templates)
 230  
     {
 231  0
         return parse(new TemplateCallback()
 232  0
         {
 233  
             public Object match(String token)
 234  
             {
 235  0
                 return props.get(token);
 236  
             }
 237  
         }, templates);
 238  
     }
 239  
 
 240  
     public Map parse(TemplateCallback callback, Map templates)
 241  
     {
 242  0
         if (templates == null)
 243  
         {
 244  0
             return new HashMap();
 245  
         }
 246  0
         Map map = new HashMap(templates.size());
 247  
         Map.Entry entry;
 248  0
         for (Iterator iterator = templates.entrySet().iterator(); iterator.hasNext();)
 249  
         {
 250  0
             entry = (Map.Entry) iterator.next();
 251  0
             map.put(entry.getKey(), parse(callback, entry.getValue().toString()));
 252  
         }
 253  0
         return map;
 254  
     }
 255  
 
 256  
     private String escape(String string)
 257  
     {
 258  0
         int length = string.length();
 259  0
         if (length == 0)
 260  
         {
 261  
             // nothing to do
 262  0
             return string;
 263  
         }
 264  
         else
 265  
         {
 266  0
             StringBuffer buffer = new StringBuffer(length * 2);
 267  0
             for (int i = 0; i < length; i++)
 268  
             {
 269  0
                 char currentCharacter = string.charAt(i);
 270  0
                 switch (currentCharacter)
 271  
                 {
 272  
                     case '[':
 273  
                     case ']':
 274  
                     case '{':
 275  
                     case '}':
 276  
                     case '(':
 277  
                     case ')':
 278  
                     case '$':
 279  
                     case '#':
 280  
                     case '*':
 281  0
                         buffer.append("\\");
 282  
                         // fall through to append original character
 283  
                     default:
 284  0
                         buffer.append(currentCharacter);
 285  
                 }
 286  
             }
 287  0
             return buffer.toString();
 288  
         }
 289  
     }
 290  
 
 291  
     public PatternInfo getStyle()
 292  
     {
 293  0
         return style;
 294  
     }
 295  
 
 296  
     public boolean isContainsTemplate(String value)
 297  
     {
 298  0
         if (value == null)
 299  
         {
 300  0
             return false;
 301  
         }
 302  
 
 303  0
         Matcher m = pattern.matcher(value);
 304  0
         return m.find();
 305  
     }
 306  
 
 307  
     public boolean isValid(String expression)
 308  
     {
 309  
         try
 310  
         {
 311  0
             style.validate(expression);
 312  0
             return true;
 313  
         }
 314  0
         catch (IllegalArgumentException e)
 315  
         {
 316  0
             return false;
 317  
         }
 318  
     }
 319  
 
 320  
     public void validate(String expression) throws IllegalArgumentException
 321  
     {
 322  0
         style.validate(expression);
 323  0
     }
 324  
 
 325  
     public static interface TemplateCallback
 326  
     {
 327  
         Object match(String token);
 328  
     }
 329  
 
 330  
 
 331  
     public static class PatternInfo
 332  
     {
 333  
         String name;
 334  
         String regEx;
 335  
         String prefix;
 336  
         String suffix;
 337  
 
 338  
         PatternInfo(String name, String regEx, String prefix, String suffix)
 339  0
         {
 340  0
             this.name = name;
 341  0
             this.regEx = regEx;
 342  0
             if (prefix.length() < 1 || prefix.length() > 2)
 343  
             {
 344  0
                 throw new IllegalArgumentException("Prefix can only be one or two characters long: " + prefix);
 345  
             }
 346  0
             this.prefix = prefix;
 347  0
             if (suffix.length() != 1)
 348  
             {
 349  0
                 throw new IllegalArgumentException("Suffic can only be one character long: " + suffix);
 350  
             }
 351  0
             this.suffix = suffix;
 352  0
         }
 353  
 
 354  
         public String getRegEx()
 355  
         {
 356  0
             return regEx;
 357  
         }
 358  
 
 359  
         public String getPrefix()
 360  
         {
 361  0
             return prefix;
 362  
         }
 363  
 
 364  
         public String getSuffix()
 365  
         {
 366  0
             return suffix;
 367  
         }
 368  
 
 369  
         public String getName()
 370  
         {
 371  0
             return name;
 372  
         }
 373  
 
 374  
         public Pattern getPattern()
 375  
         {
 376  0
             return Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
 377  
         }
 378  
 
 379  
         public void validate(String expression) throws IllegalArgumentException
 380  
         {
 381  0
             Stack<Character> openDelimiterStack = new Stack<Character>();
 382  
 
 383  0
             int charCount = expression.length();
 384  0
             int index = 0;
 385  0
             char nextChar = ' ';
 386  0
             char preDelim = 0;
 387  
             char open;
 388  
             char close;
 389  0
             boolean inExpression = false;
 390  0
             int expressionCount = 0;
 391  0
             if (prefix.length() == 2)
 392  
             {
 393  0
                 preDelim = prefix.charAt(0);
 394  0
                 open = prefix.charAt(1);
 395  
             }
 396  
             else
 397  
             {
 398  0
                 open = prefix.charAt(0);
 399  
             }
 400  0
             close = suffix.charAt(0);
 401  
 
 402  0
             for (; index < charCount; index++)
 403  
             {
 404  0
                 nextChar = expression.charAt(index);
 405  0
                 if (preDelim != 0 && nextChar == preDelim)
 406  
                 {
 407  
                     //escaped
 408  0
                     if (inExpression)
 409  
                     {
 410  0
                         if (index < charCount && expression.charAt(index + 1) == open)
 411  
                         {
 412  0
                             throw new IllegalArgumentException(String.format("Character %s at position %s suggests an expression inside an expression", open, index));
 413  
                         }
 414  
                     }
 415  0
                     else if (openDelimiterStack.isEmpty())
 416  
                     {
 417  0
                         openDelimiterStack.push(nextChar);
 418  0
                         nextChar = expression.charAt(++index);
 419  0
                         if (nextChar != open)
 420  
                         {
 421  0
                             throw new IllegalArgumentException(String.format("Character %s at position %s must appear immediately after %s", open, index, preDelim));
 422  
                         }
 423  0
                         inExpression = true;
 424  
 
 425  
                     }
 426  
                     else
 427  
                     {
 428  0
                         throw new IllegalArgumentException(String.format("Character %s at position %s appears out of sequence. Character cannot appear after %s", nextChar, index, openDelimiterStack.pop()));
 429  
                     }
 430  
                 }
 431  
 
 432  0
                 if (nextChar == open)
 433  
                 {
 434  0
                     if (preDelim == 0 || inExpression)
 435  
                     {
 436  0
                         openDelimiterStack.push(nextChar);
 437  
                     }
 438  
                     //Check the stack size to avoid out of bounds
 439  0
                     else if (openDelimiterStack.size() == 1 && openDelimiterStack.peek().equals(preDelim))
 440  
                     {
 441  0
                         openDelimiterStack.push(nextChar);
 442  
                     }
 443  
                     else
 444  
                     {
 445  0
                         throw new IllegalArgumentException(String.format("Character %s at position %s appears out of sequence. Character cannot appear after %s", nextChar, index, preDelim));
 446  
                     }
 447  
                 }
 448  0
                 else if (nextChar == close)
 449  
                 {
 450  0
                     if (openDelimiterStack.isEmpty())
 451  
                     {
 452  0
                         throw new IllegalArgumentException(String.format("Character %s at position %s appears out of sequence", nextChar, index));
 453  
                     }
 454  
                     else
 455  
                     {
 456  0
                         openDelimiterStack.pop();
 457  0
                         if (preDelim != 0 && openDelimiterStack.peek() == preDelim)
 458  
                         {
 459  0
                             openDelimiterStack.pop();
 460  
                         }
 461  
 
 462  
 
 463  0
                         if (openDelimiterStack.isEmpty())
 464  
                         {
 465  0
                             inExpression = false;
 466  0
                             expressionCount++;
 467  
                             //throw new IllegalArgumentException(String.format("Character %s at position %s appears out of sequence", nextChar, index));
 468  
                         }
 469  
                     }
 470  
                 }
 471  
             }
 472  0
             if (expressionCount == 0)
 473  
             {
 474  0
                 throw new IllegalArgumentException("Not an expression: " + expression);
 475  
             }
 476  0
         }
 477  
 
 478  
     }
 479  
 }