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