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