1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.log4j.pattern;
19
20 import org.apache.log4j.helpers.Loader;
21 import org.apache.log4j.helpers.LogLog;
22
23 import java.lang.reflect.Method;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30
31
32
33
34
35 /***
36 * Most of the work of the {@link org.apache.log4j.EnhancedPatternLayout} class
37 * is delegated to the PatternParser class.
38 * <p>It is this class that parses conversion patterns and creates
39 * a chained list of {@link PatternConverter PatternConverters}.
40 *
41 * @author James P. Cakalic
42 * @author Ceki Gülcü
43 * @author Anders Kristensen
44 * @author Paul Smith
45 * @author Curt Arnold
46 *
47 */
48 public final class PatternParser {
49 /***
50 * Escape character for format specifier.
51 */
52 private static final char ESCAPE_CHAR = '%';
53
54 /***
55 * Literal state.
56 */
57 private static final int LITERAL_STATE = 0;
58
59 /***
60 * In converter name state.
61 */
62 private static final int CONVERTER_STATE = 1;
63
64 /***
65 * Dot state.
66 */
67 private static final int DOT_STATE = 3;
68
69 /***
70 * Min state.
71 */
72 private static final int MIN_STATE = 4;
73
74 /***
75 * Max state.
76 */
77 private static final int MAX_STATE = 5;
78
79 /***
80 * Standard format specifiers for EnhancedPatternLayout.
81 */
82 private static final Map PATTERN_LAYOUT_RULES;
83
84 /***
85 * Standard format specifiers for rolling file appenders.
86 */
87 private static final Map FILENAME_PATTERN_RULES;
88
89 static {
90
91 Map rules = new HashMap(17);
92 rules.put("c", LoggerPatternConverter.class);
93 rules.put("logger", LoggerPatternConverter.class);
94
95 rules.put("C", ClassNamePatternConverter.class);
96 rules.put("class", ClassNamePatternConverter.class);
97
98 rules.put("d", DatePatternConverter.class);
99 rules.put("date", DatePatternConverter.class);
100
101 rules.put("F", FileLocationPatternConverter.class);
102 rules.put("file", FileLocationPatternConverter.class);
103
104 rules.put("l", FullLocationPatternConverter.class);
105
106 rules.put("L", LineLocationPatternConverter.class);
107 rules.put("line", LineLocationPatternConverter.class);
108
109 rules.put("m", MessagePatternConverter.class);
110 rules.put("message", MessagePatternConverter.class);
111
112 rules.put("n", LineSeparatorPatternConverter.class);
113
114 rules.put("M", MethodLocationPatternConverter.class);
115 rules.put("method", MethodLocationPatternConverter.class);
116
117 rules.put("p", LevelPatternConverter.class);
118 rules.put("level", LevelPatternConverter.class);
119
120 rules.put("r", RelativeTimePatternConverter.class);
121 rules.put("relative", RelativeTimePatternConverter.class);
122
123 rules.put("t", ThreadPatternConverter.class);
124 rules.put("thread", ThreadPatternConverter.class);
125
126 rules.put("x", NDCPatternConverter.class);
127 rules.put("ndc", NDCPatternConverter.class);
128
129 rules.put("X", PropertiesPatternConverter.class);
130 rules.put("properties", PropertiesPatternConverter.class);
131
132 rules.put("sn", SequenceNumberPatternConverter.class);
133 rules.put("sequenceNumber", SequenceNumberPatternConverter.class);
134
135 rules.put("throwable", ThrowableInformationPatternConverter.class);
136 PATTERN_LAYOUT_RULES = new ReadOnlyMap(rules);
137
138 Map fnameRules = new HashMap(4);
139 fnameRules.put("d", FileDatePatternConverter.class);
140 fnameRules.put("date", FileDatePatternConverter.class);
141 fnameRules.put("i", IntegerPatternConverter.class);
142 fnameRules.put("index", IntegerPatternConverter.class);
143
144 FILENAME_PATTERN_RULES = new ReadOnlyMap(fnameRules);
145 }
146
147 /***
148 * Private constructor.
149 */
150 private PatternParser() {
151 }
152
153 /***
154 * Get standard format specifiers for EnhancedPatternLayout.
155 * @return read-only map of format converter classes keyed by format specifier strings.
156 */
157 public static Map getPatternLayoutRules() {
158 return PATTERN_LAYOUT_RULES;
159 }
160
161 /***
162 * Get standard format specifiers for rolling file appender file specification.
163 * @return read-only map of format converter classes keyed by format specifier strings.
164 */
165 public static Map getFileNamePatternRules() {
166 return FILENAME_PATTERN_RULES;
167 }
168
169 /*** Extract the converter identifier found at position i.
170 *
171 * After this function returns, the variable i will point to the
172 * first char after the end of the converter identifier.
173 *
174 * If i points to a char which is not a character acceptable at the
175 * start of a unicode identifier, the value null is returned.
176 *
177 * @param lastChar last processed character.
178 * @param pattern format string.
179 * @param i current index into pattern format.
180 * @param convBuf buffer to receive conversion specifier.
181 * @param currentLiteral literal to be output in case format specifier in unrecognized.
182 * @return position in pattern after converter.
183 */
184 private static int extractConverter(
185 char lastChar, final String pattern, int i, final StringBuffer convBuf,
186 final StringBuffer currentLiteral) {
187 convBuf.setLength(0);
188
189
190
191
192
193
194 if (!Character.isUnicodeIdentifierStart(lastChar)) {
195 return i;
196 }
197
198 convBuf.append(lastChar);
199
200 while (
201 (i < pattern.length())
202 && Character.isUnicodeIdentifierPart(pattern.charAt(i))) {
203 convBuf.append(pattern.charAt(i));
204 currentLiteral.append(pattern.charAt(i));
205
206
207 i++;
208 }
209
210 return i;
211 }
212
213 /***
214 * Extract options.
215 * @param pattern conversion pattern.
216 * @param i start of options.
217 * @param options array to receive extracted options
218 * @return position in pattern after options.
219 */
220 private static int extractOptions(String pattern, int i, List options) {
221 while ((i < pattern.length()) && (pattern.charAt(i) == '{')) {
222 int end = pattern.indexOf('}', i);
223
224 if (end == -1) {
225 break;
226 }
227
228 String r = pattern.substring(i + 1, end);
229 options.add(r);
230 i = end + 1;
231 }
232
233 return i;
234 }
235
236 /***
237 * Parse a format specifier.
238 * @param pattern pattern to parse.
239 * @param patternConverters list to receive pattern converters.
240 * @param formattingInfos list to receive field specifiers corresponding to pattern converters.
241 * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
242 * @param rules map of stock pattern converters keyed by format specifier.
243 */
244 public static void parse(
245 final String pattern, final List patternConverters,
246 final List formattingInfos, final Map converterRegistry, final Map rules) {
247 if (pattern == null) {
248 throw new NullPointerException("pattern");
249 }
250
251 StringBuffer currentLiteral = new StringBuffer(32);
252
253 int patternLength = pattern.length();
254 int state = LITERAL_STATE;
255 char c;
256 int i = 0;
257 FormattingInfo formattingInfo = FormattingInfo.getDefault();
258
259 while (i < patternLength) {
260 c = pattern.charAt(i++);
261
262 switch (state) {
263 case LITERAL_STATE:
264
265
266 if (i == patternLength) {
267 currentLiteral.append(c);
268
269 continue;
270 }
271
272 if (c == ESCAPE_CHAR) {
273
274 switch (pattern.charAt(i)) {
275 case ESCAPE_CHAR:
276 currentLiteral.append(c);
277 i++;
278
279 break;
280
281 default:
282
283 if (currentLiteral.length() != 0) {
284 patternConverters.add(
285 new LiteralPatternConverter(currentLiteral.toString()));
286 formattingInfos.add(FormattingInfo.getDefault());
287 }
288
289 currentLiteral.setLength(0);
290 currentLiteral.append(c);
291 state = CONVERTER_STATE;
292 formattingInfo = FormattingInfo.getDefault();
293 }
294 } else {
295 currentLiteral.append(c);
296 }
297
298 break;
299
300 case CONVERTER_STATE:
301 currentLiteral.append(c);
302
303 switch (c) {
304 case '-':
305 formattingInfo =
306 new FormattingInfo(
307 true, formattingInfo.getMinLength(),
308 formattingInfo.getMaxLength());
309
310 break;
311
312 case '.':
313 state = DOT_STATE;
314
315 break;
316
317 default:
318
319 if ((c >= '0') && (c <= '9')) {
320 formattingInfo =
321 new FormattingInfo(
322 formattingInfo.isLeftAligned(), c - '0',
323 formattingInfo.getMaxLength());
324 state = MIN_STATE;
325 } else {
326 i = finalizeConverter(
327 c, pattern, i, currentLiteral, formattingInfo,
328 converterRegistry, rules, patternConverters, formattingInfos);
329
330
331 state = LITERAL_STATE;
332 formattingInfo = FormattingInfo.getDefault();
333 currentLiteral.setLength(0);
334 }
335 }
336
337 break;
338
339 case MIN_STATE:
340 currentLiteral.append(c);
341
342 if ((c >= '0') && (c <= '9')) {
343 formattingInfo =
344 new FormattingInfo(
345 formattingInfo.isLeftAligned(),
346 (formattingInfo.getMinLength() * 10) + (c - '0'),
347 formattingInfo.getMaxLength());
348 } else if (c == '.') {
349 state = DOT_STATE;
350 } else {
351 i = finalizeConverter(
352 c, pattern, i, currentLiteral, formattingInfo,
353 converterRegistry, rules, patternConverters, formattingInfos);
354 state = LITERAL_STATE;
355 formattingInfo = FormattingInfo.getDefault();
356 currentLiteral.setLength(0);
357 }
358
359 break;
360
361 case DOT_STATE:
362 currentLiteral.append(c);
363
364 if ((c >= '0') && (c <= '9')) {
365 formattingInfo =
366 new FormattingInfo(
367 formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
368 c - '0');
369 state = MAX_STATE;
370 } else {
371 LogLog.error(
372 "Error occured in position " + i
373 + ".\n Was expecting digit, instead got char \"" + c + "\".");
374
375 state = LITERAL_STATE;
376 }
377
378 break;
379
380 case MAX_STATE:
381 currentLiteral.append(c);
382
383 if ((c >= '0') && (c <= '9')) {
384 formattingInfo =
385 new FormattingInfo(
386 formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
387 (formattingInfo.getMaxLength() * 10) + (c - '0'));
388 } else {
389 i = finalizeConverter(
390 c, pattern, i, currentLiteral, formattingInfo,
391 converterRegistry, rules, patternConverters, formattingInfos);
392 state = LITERAL_STATE;
393 formattingInfo = FormattingInfo.getDefault();
394 currentLiteral.setLength(0);
395 }
396
397 break;
398 }
399 }
400
401
402 if (currentLiteral.length() != 0) {
403 patternConverters.add(
404 new LiteralPatternConverter(currentLiteral.toString()));
405 formattingInfos.add(FormattingInfo.getDefault());
406 }
407 }
408
409 /***
410 * Creates a new PatternConverter.
411 *
412 *
413 * @param converterId converterId.
414 * @param currentLiteral literal to be used if converter is unrecognized or following converter
415 * if converterId contains extra characters.
416 * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
417 * @param rules map of stock pattern converters keyed by format specifier.
418 * @param options converter options.
419 * @return converter or null.
420 */
421 private static PatternConverter createConverter(
422 final String converterId, final StringBuffer currentLiteral,
423 final Map converterRegistry, final Map rules, final List options) {
424 String converterName = converterId;
425 Object converterObj = null;
426
427 for (int i = converterId.length(); (i > 0) && (converterObj == null);
428 i--) {
429 converterName = converterName.substring(0, i);
430
431 if (converterRegistry != null) {
432 converterObj = converterRegistry.get(converterName);
433 }
434
435 if ((converterObj == null) && (rules != null)) {
436 converterObj = rules.get(converterName);
437 }
438 }
439
440 if (converterObj == null) {
441 LogLog.error("Unrecognized format specifier [" + converterId + "]");
442
443 return null;
444 }
445
446 Class converterClass = null;
447
448 if (converterObj instanceof Class) {
449 converterClass = (Class) converterObj;
450 } else {
451 if (converterObj instanceof String) {
452 try {
453 converterClass = Loader.loadClass((String) converterObj);
454 } catch (ClassNotFoundException ex) {
455 LogLog.warn(
456 "Class for conversion pattern %" + converterName + " not found",
457 ex);
458
459 return null;
460 }
461 } else {
462 LogLog.warn(
463 "Bad map entry for conversion pattern %" + converterName + ".");
464
465 return null;
466 }
467 }
468
469 try {
470 Method factory =
471 converterClass.getMethod(
472 "newInstance",
473 new Class[] {
474 Class.forName("[Ljava.lang.String;")
475 });
476 String[] optionsArray = new String[options.size()];
477 optionsArray = (String[]) options.toArray(optionsArray);
478
479 Object newObj =
480 factory.invoke(null, new Object[] { optionsArray });
481
482 if (newObj instanceof PatternConverter) {
483 currentLiteral.delete(
484 0,
485 currentLiteral.length()
486 - (converterId.length() - converterName.length()));
487
488 return (PatternConverter) newObj;
489 } else {
490 LogLog.warn(
491 "Class " + converterClass.getName()
492 + " does not extend PatternConverter.");
493 }
494 } catch (Exception ex) {
495 LogLog.error("Error creating converter for " + converterId, ex);
496
497 try {
498
499
500 PatternConverter pc = (PatternConverter) converterClass.newInstance();
501 currentLiteral.delete(
502 0,
503 currentLiteral.length()
504 - (converterId.length() - converterName.length()));
505
506 return pc;
507 } catch (Exception ex2) {
508 LogLog.error("Error creating converter for " + converterId, ex2);
509 }
510 }
511
512 return null;
513 }
514
515 /***
516 * Processes a format specifier sequence.
517 *
518 * @param c initial character of format specifier.
519 * @param pattern conversion pattern
520 * @param i current position in conversion pattern.
521 * @param currentLiteral current literal.
522 * @param formattingInfo current field specifier.
523 * @param converterRegistry map of user-provided pattern converters keyed by format specifier, may be null.
524 * @param rules map of stock pattern converters keyed by format specifier.
525 * @param patternConverters list to receive parsed pattern converter.
526 * @param formattingInfos list to receive corresponding field specifier.
527 * @return position after format specifier sequence.
528 */
529 private static int finalizeConverter(
530 char c, String pattern, int i,
531 final StringBuffer currentLiteral, final FormattingInfo formattingInfo,
532 final Map converterRegistry, final Map rules, final List patternConverters,
533 final List formattingInfos) {
534 StringBuffer convBuf = new StringBuffer();
535 i = extractConverter(c, pattern, i, convBuf, currentLiteral);
536
537 String converterId = convBuf.toString();
538
539 List options = new ArrayList();
540 i = extractOptions(pattern, i, options);
541
542 PatternConverter pc =
543 createConverter(
544 converterId, currentLiteral, converterRegistry, rules, options);
545
546 if (pc == null) {
547 StringBuffer msg;
548
549 if ((converterId == null) || (converterId.length() == 0)) {
550 msg =
551 new StringBuffer("Empty conversion specifier starting at position ");
552 } else {
553 msg = new StringBuffer("Unrecognized conversion specifier [");
554 msg.append(converterId);
555 msg.append("] starting at position ");
556 }
557
558 msg.append(Integer.toString(i));
559 msg.append(" in conversion pattern.");
560
561 LogLog.error(msg.toString());
562
563 patternConverters.add(
564 new LiteralPatternConverter(currentLiteral.toString()));
565 formattingInfos.add(FormattingInfo.getDefault());
566 } else {
567 patternConverters.add(pc);
568 formattingInfos.add(formattingInfo);
569
570 if (currentLiteral.length() > 0) {
571 patternConverters.add(
572 new LiteralPatternConverter(currentLiteral.toString()));
573 formattingInfos.add(FormattingInfo.getDefault());
574 }
575 }
576
577 currentLiteral.setLength(0);
578
579 return i;
580 }
581
582 /***
583 * The class wraps another Map but throws exceptions on any attempt to modify the map.
584 */
585 private static class ReadOnlyMap implements Map {
586 /***
587 * Wrapped map.
588 */
589 private final Map map;
590
591 /***
592 * Constructor
593 * @param src source map.
594 */
595 public ReadOnlyMap(Map src) {
596 map = src;
597 }
598
599 /***
600 * {@inheritDoc}
601 */
602 public void clear() {
603 throw new UnsupportedOperationException();
604 }
605
606 /***
607 * {@inheritDoc}
608 */
609 public boolean containsKey(Object key) {
610 return map.containsKey(key);
611 }
612
613 /***
614 * {@inheritDoc}
615 */
616 public boolean containsValue(Object value) {
617 return map.containsValue(value);
618 }
619
620 /***
621 * {@inheritDoc}
622 */
623 public Set entrySet() {
624 return map.entrySet();
625 }
626
627 /***
628 * {@inheritDoc}
629 */
630 public Object get(Object key) {
631 return map.get(key);
632 }
633
634 /***
635 * {@inheritDoc}
636 */
637 public boolean isEmpty() {
638 return map.isEmpty();
639 }
640
641 /***
642 * {@inheritDoc}
643 */
644 public Set keySet() {
645 return map.keySet();
646 }
647
648 /***
649 * {@inheritDoc}
650 */
651 public Object put(Object key, Object value) {
652 throw new UnsupportedOperationException();
653 }
654
655 /***
656 * {@inheritDoc}
657 */
658 public void putAll(Map t) {
659 throw new UnsupportedOperationException();
660 }
661
662 /***
663 * {@inheritDoc}
664 */
665 public Object remove(Object key) {
666 throw new UnsupportedOperationException();
667 }
668
669 /***
670 * {@inheritDoc}
671 */
672 public int size() {
673 return map.size();
674 }
675
676 /***
677 * {@inheritDoc}
678 */
679 public Collection values() {
680 return map.values();
681 }
682 }
683 }