Coverage Report - org.mule.util.TemplateParser
 
Classes in this File Line Coverage Branch Coverage Complexity
TemplateParser
0%
0/77
0%
0/34
0
TemplateParser$1
0%
0/2
N/A
0
TemplateParser$PatternInfo
0%
0/35
0%
0/14
0
TemplateParser$TemplateCallback
N/A
N/A
0
 
 1  
 /*
 2  
  * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 3  
  * The software in this package is published under the terms of the CPAL v1.0
 4  
  * license, a copy of which has been included with this distribution in the
 5  
  * LICENSE.txt file.
 6  
  */
 7  
 package org.mule.util;
 8  
 
 9  
 import java.util.ArrayList;
 10  
 import java.util.HashMap;
 11  
 import java.util.List;
 12  
 import java.util.Map;
 13  
 import java.util.Stack;
 14  
 import java.util.regex.Matcher;
 15  
 import java.util.regex.Pattern;
 16  
 
 17  
 import org.apache.commons.logging.Log;
 18  
 import org.apache.commons.logging.LogFactory;
 19  
 
 20  
 /**
 21  
  * <code>TemplateParser</code> is a simple string parser that will substitute
 22  
  * tokens in a string with values supplied in a Map.
 23  
  */
 24  
 public final class TemplateParser
 25  
 {
 26  
     public static final String ANT_TEMPLATE_STYLE = "ant";
 27  
     public static final String SQUARE_TEMPLATE_STYLE = "square";
 28  
     public static final String CURLY_TEMPLATE_STYLE = "curly";
 29  
     public static final String WIGGLY_MULE_TEMPLATE_STYLE = "mule";
 30  
 
 31  
     private static final String DOLLAR_ESCAPE = "@@@";
 32  
     private static final String NULL_AS_STRING = "null";
 33  
 
 34  0
     private static final Map<String, PatternInfo> patterns = new HashMap<String, PatternInfo>();
 35  
 
 36  
     static
 37  
     {
 38  0
         patterns.put(ANT_TEMPLATE_STYLE, new PatternInfo(ANT_TEMPLATE_STYLE, "\\$\\{[^\\{\\}]+\\}", "${", "}"));
 39  0
         patterns.put(SQUARE_TEMPLATE_STYLE, new PatternInfo(SQUARE_TEMPLATE_STYLE, "\\[[^\\[\\]]+\\]", "[", "]"));
 40  0
         patterns.put(CURLY_TEMPLATE_STYLE, new PatternInfo(CURLY_TEMPLATE_STYLE, "\\{[^\\{\\}}]+\\}", "{", "}"));
 41  
 
 42  
         // Such a complex regex is needed to support nested expressions, otherwise we
 43  
         // have to do this manually or using an ANTLR grammar etc.
 44  
 
 45  
         // Support for 6 levels (5 nested)
 46  0
         patterns.put(WIGGLY_MULE_TEMPLATE_STYLE, new PatternInfo(WIGGLY_MULE_TEMPLATE_STYLE,
 47  
         "#\\[((?:#?\\[(?:#?\\[(?:#?\\[(?:#?\\[(?:#?\\[.*?\\]|[^\\[\\]])*?\\]|[^\\[\\]])*?\\]|[^\\[\\]])*?\\]|[^\\[\\]])*?\\]|[^\\[\\]])*?)\\]", "#[", "]"));
 48  
     }
 49  
 
 50  
     /**
 51  
      * logger used by this class
 52  
      */
 53  0
     protected static final Log logger = LogFactory.getLog(TemplateParser.class);
 54  
 
 55  0
     public static final Pattern ANT_TEMPLATE_PATTERN = patterns.get(ANT_TEMPLATE_STYLE).getPattern();
 56  0
     public static final Pattern SQUARE_TEMPLATE_PATTERN = patterns.get(SQUARE_TEMPLATE_STYLE).getPattern();
 57  0
     public static final Pattern CURLY_TEMPLATE_PATTERN = patterns.get(CURLY_TEMPLATE_STYLE).getPattern();
 58  0
     public static final Pattern WIGGLY_MULE_TEMPLATE_PATTERN = patterns.get(WIGGLY_MULE_TEMPLATE_STYLE).getPattern();
 59  
 
 60  
     private final Pattern pattern;
 61  
     private final int pre;
 62  
     private final int post;
 63  
     private final PatternInfo style;
 64  
 
 65  
 
 66  
     public static TemplateParser createAntStyleParser()
 67  
     {
 68  0
         return new TemplateParser(ANT_TEMPLATE_STYLE);
 69  
     }
 70  
 
 71  
     public static TemplateParser createSquareBracesStyleParser()
 72  
     {
 73  0
         return new TemplateParser(SQUARE_TEMPLATE_STYLE);
 74  
     }
 75  
 
 76  
     public static TemplateParser createCurlyBracesStyleParser()
 77  
     {
 78  0
         return new TemplateParser(CURLY_TEMPLATE_STYLE);
 79  
     }
 80  
 
 81  
     public static TemplateParser createMuleStyleParser()
 82  
     {
 83  0
         return new TemplateParser(WIGGLY_MULE_TEMPLATE_STYLE);
 84  
     }
 85  
 
 86  
     private TemplateParser(String styleName)
 87  0
     {
 88  0
         this.style = patterns.get(styleName);
 89  0
         if (this.style == null)
 90  
         {
 91  0
             throw new IllegalArgumentException("Unknown template style: " + styleName);
 92  
 
 93  
         }
 94  0
         pattern = style.getPattern();
 95  0
         pre = style.getPrefix().length();
 96  0
         post = style.getSuffix().length();
 97  0
     }
 98  
 
 99  
     /**
 100  
      * Matches one or more templates against a Map of key value pairs. If a value for
 101  
      * a template is not found in the map the template is left as is in the return
 102  
      * String
 103  
      *
 104  
      * @param props    the key/value pairs to match against
 105  
      * @param template the string containing the template place holders i.e. My name
 106  
      *                 is ${name}
 107  
      * @return the parsed String
 108  
      */
 109  
     public String parse(Map<?, ?> props, String template)
 110  
     {
 111  0
         return parse(props, template, null);
 112  
     }
 113  
 
 114  
     /**
 115  
      * Matches one or more templates against a Map of key value pairs. If a value for
 116  
      * a template is not found in the map the template is left as is in the return
 117  
      * String
 118  
      *
 119  
      * @param callback a callback used to resolve the property name
 120  
      * @param template the string containing the template place holders i.e. My name
 121  
      *                 is ${name}
 122  
      * @return the parsed String
 123  
      */
 124  
     public String parse(TemplateCallback callback, String template)
 125  
     {
 126  0
         return parse(null, template, callback);
 127  
     }
 128  
 
 129  
     protected String parse(Map<?, ?> props, String template, TemplateCallback callback)
 130  
     {
 131  0
         String result = template;
 132  0
         Map<?, ?> newProps = props;
 133  0
         if (props != null && !(props instanceof CaseInsensitiveHashMap))
 134  
         {
 135  0
             newProps = new CaseInsensitiveHashMap(props);
 136  
         }
 137  
 
 138  0
         Matcher m = pattern.matcher(result);
 139  
 
 140  0
         while (m.find())
 141  
         {
 142  0
             Object value = null;
 143  
 
 144  0
             String match = m.group();
 145  0
             String propname = match.substring(pre, match.length() - post);
 146  
 
 147  0
             if (callback != null)
 148  
             {
 149  0
                 value = callback.match(propname);
 150  0
                 if (value == null)
 151  
                 {
 152  0
                     value = NULL_AS_STRING;
 153  
                 }
 154  
             }
 155  0
             else if (newProps != null)
 156  
             {
 157  0
                 value = newProps.get(propname);
 158  
             }
 159  
 
 160  0
             if (value == null)
 161  
             {
 162  0
                 if (logger.isDebugEnabled())
 163  
                 {
 164  0
                     logger.debug("Value " + propname + " not found in context");
 165  
                 }
 166  
             }
 167  
             else
 168  
             {
 169  0
                 String matchRegex = Pattern.quote(match);
 170  0
                 String valueString = value.toString();
 171  
                 //need to escape $ as they resolve into group references, escaping them was not enough
 172  
                 //This smells a bit like a hack, but one way or another these characters need to be escaped
 173  0
                 if (valueString.indexOf('$') != -1)
 174  
                 {
 175  0
                     valueString = valueString.replaceAll("\\$", DOLLAR_ESCAPE);
 176  
                 }
 177  
 
 178  0
                 if (valueString.indexOf('\\') != -1)
 179  
                 {
 180  0
                     valueString = valueString.replaceAll("\\\\", "\\\\\\\\");
 181  
                 }
 182  
 
 183  0
                 result = result.replaceAll(matchRegex, valueString);
 184  
             }
 185  0
         }
 186  0
         if (result.indexOf(DOLLAR_ESCAPE) != -1)
 187  
         {
 188  0
             result = result.replaceAll(DOLLAR_ESCAPE, "\\$");
 189  
         }
 190  0
         return result;
 191  
     }
 192  
 
 193  
     /**
 194  
      * Matches one or more templates against a Map of key value pairs. If a value for
 195  
      * a template is not found in the map the template is left as is in the return
 196  
      * String
 197  
      *
 198  
      * @param props     the key/value pairs to match against
 199  
      * @param templates A List of templates
 200  
      * @return the parsed String
 201  
      */
 202  
     public List<?> parse(Map<?, ?> props, List<?> templates)
 203  
     {
 204  0
         if (templates == null)
 205  
         {
 206  0
             return new ArrayList<Object>();
 207  
         }
 208  
 
 209  0
         List<String> list = new ArrayList<String>(templates.size());
 210  0
         for (Object tmpl : templates)
 211  
         {
 212  0
             list.add(parse(props, tmpl.toString()));
 213  
         }
 214  0
         return list;
 215  
     }
 216  
 
 217  
     /**
 218  
      * Matches one or more templates against a Map of key value pairs. If a value for
 219  
      * a template is not found in the map the template is left as is in the return
 220  
      * String
 221  
      *
 222  
      * @param props     the key/value pairs to match against
 223  
      * @param templates A Map of templates. The values for each map entry will be
 224  
      *                  parsed
 225  
      * @return the parsed String
 226  
      */
 227  
     public Map<?, ?> parse(final Map<?, ?> props, Map<?, ?> templates)
 228  
     {
 229  0
         return parse(new TemplateCallback()
 230  0
         {
 231  
             public Object match(String token)
 232  
             {
 233  0
                 return props.get(token);
 234  
             }
 235  
         }, templates);
 236  
     }
 237  
 
 238  
     public Map<?, ?> parse(TemplateCallback callback, Map<?, ?> templates)
 239  
     {
 240  0
         if (templates == null)
 241  
         {
 242  0
             return new HashMap<Object, Object>();
 243  
         }
 244  
 
 245  0
         Map<Object, String> map = new HashMap<Object, String>(templates.size());
 246  0
         for (Map.Entry<?, ?> entry : templates.entrySet())
 247  
         {
 248  0
             map.put(entry.getKey(), parse(callback, entry.getValue().toString()));
 249  
         }
 250  0
         return map;
 251  
     }
 252  
 
 253  
     public PatternInfo getStyle()
 254  
     {
 255  0
         return style;
 256  
     }
 257  
 
 258  
     public boolean isContainsTemplate(String value)
 259  
     {
 260  0
         if (value == null)
 261  
         {
 262  0
             return false;
 263  
         }
 264  
 
 265  0
         Matcher m = pattern.matcher(value);
 266  0
         return m.find();
 267  
     }
 268  
 
 269  
     public boolean isValid(String expression)
 270  
     {
 271  
         try
 272  
         {
 273  0
             style.validate(expression);
 274  0
             return true;
 275  
         }
 276  0
         catch (IllegalArgumentException e)
 277  
         {
 278  0
             return false;
 279  
         }
 280  
     }
 281  
 
 282  
     public void validate(String expression) throws IllegalArgumentException
 283  
     {
 284  0
         style.validate(expression);
 285  0
     }
 286  
 
 287  
     public static interface TemplateCallback
 288  
     {
 289  
         Object match(String token);
 290  
     }
 291  
 
 292  
 
 293  
     public static class PatternInfo
 294  
     {
 295  
         String name;
 296  
         String regEx;
 297  
         String prefix;
 298  
         String suffix;
 299  
 
 300  
         PatternInfo(String name, String regEx, String prefix, String suffix)
 301  0
         {
 302  0
             this.name = name;
 303  0
             this.regEx = regEx;
 304  0
             if (prefix.length() < 1 || prefix.length() > 2)
 305  
             {
 306  0
                 throw new IllegalArgumentException("Prefix can only be one or two characters long: " + prefix);
 307  
             }
 308  0
             this.prefix = prefix;
 309  0
             if (suffix.length() != 1)
 310  
             {
 311  0
                 throw new IllegalArgumentException("Suffix can only be one character long: " + suffix);
 312  
             }
 313  0
             this.suffix = suffix;
 314  0
         }
 315  
 
 316  
         public String getRegEx()
 317  
         {
 318  0
             return regEx;
 319  
         }
 320  
 
 321  
         public String getPrefix()
 322  
         {
 323  0
             return prefix;
 324  
         }
 325  
 
 326  
         public String getSuffix()
 327  
         {
 328  0
             return suffix;
 329  
         }
 330  
 
 331  
         public String getName()
 332  
         {
 333  0
             return name;
 334  
         }
 335  
 
 336  
         public Pattern getPattern()
 337  
         {
 338  0
             return Pattern.compile(regEx, Pattern.CASE_INSENSITIVE);
 339  
         }
 340  
 
 341  
         public void validate(String expression) throws IllegalArgumentException
 342  
         {
 343  0
             String currentExpression = expression;
 344  0
             int lastMatchIdx = 0;
 345  0
             while (lastMatchIdx < expression.length())
 346  
             {
 347  0
                 int start = currentExpression.indexOf(prefix);
 348  0
                 if (start == -1)
 349  
                 {
 350  
                     //no more expressions to validate
 351  0
                     break;
 352  
                 }
 353  0
                 lastMatchIdx += start;
 354  0
                 currentExpression = currentExpression.substring(start);
 355  0
                 Matcher m = getPattern().matcher(currentExpression);
 356  0
                 boolean found = m.find();
 357  0
                 if (found)
 358  
                 {
 359  0
                     if (!currentExpression.startsWith(m.group()))
 360  
                     {
 361  0
                         throw new IllegalArgumentException("Invalid Expression");
 362  
                     }
 363  0
                     int matchSize = m.group().length();
 364  0
                     lastMatchIdx += matchSize;
 365  0
                     currentExpression = currentExpression.substring(matchSize);
 366  0
                 }
 367  
                 else
 368  
                 {
 369  0
                     throw new IllegalArgumentException("Invalid Expression");
 370  
                 }
 371  0
             }
 372  0
         }
 373  
 
 374  
     }
 375  
 }