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,
308 formattingInfo.isRightTruncated(),
309 formattingInfo.getMinLength(),
310 formattingInfo.getMaxLength());
311 break;
312
313 case '!':
314 formattingInfo =
315 new FormattingInfo(
316 formattingInfo.isLeftAligned(),
317 true,
318 formattingInfo.getMinLength(),
319 formattingInfo.getMaxLength());
320 break;
321
322
323 case '.':
324 state = DOT_STATE;
325
326 break;
327
328 default:
329
330 if ((c >= '0') && (c <= '9')) {
331 formattingInfo =
332 new FormattingInfo(
333 formattingInfo.isLeftAligned(),
334 formattingInfo.isRightTruncated(),
335 c - '0',
336 formattingInfo.getMaxLength());
337 state = MIN_STATE;
338 } else {
339 i = finalizeConverter(
340 c, pattern, i, currentLiteral, formattingInfo,
341 converterRegistry, rules, patternConverters, formattingInfos);
342
343
344 state = LITERAL_STATE;
345 formattingInfo = FormattingInfo.getDefault();
346 currentLiteral.setLength(0);
347 }
348 }
349
350 break;
351
352 case MIN_STATE:
353 currentLiteral.append(c);
354
355 if ((c >= '0') && (c <= '9')) {
356 formattingInfo =
357 new FormattingInfo(
358 formattingInfo.isLeftAligned(),
359 formattingInfo.isRightTruncated(),
360 (formattingInfo.getMinLength() * 10) + (c - '0'),
361 formattingInfo.getMaxLength());
362 } else if (c == '.') {
363 state = DOT_STATE;
364 } else {
365 i = finalizeConverter(
366 c, pattern, i, currentLiteral, formattingInfo,
367 converterRegistry, rules, patternConverters, formattingInfos);
368 state = LITERAL_STATE;
369 formattingInfo = FormattingInfo.getDefault();
370 currentLiteral.setLength(0);
371 }
372
373 break;
374
375 case DOT_STATE:
376 currentLiteral.append(c);
377
378 if ((c >= '0') && (c <= '9')) {
379 formattingInfo =
380 new FormattingInfo(
381 formattingInfo.isLeftAligned(),
382 formattingInfo.isRightTruncated(),
383 formattingInfo.getMinLength(),
384 c - '0');
385 state = MAX_STATE;
386 } else {
387 LogLog.error(
388 "Error occured in position " + i
389 + ".\n Was expecting digit, instead got char \"" + c + "\".");
390
391 state = LITERAL_STATE;
392 }
393
394 break;
395
396 case MAX_STATE:
397 currentLiteral.append(c);
398
399 if ((c >= '0') && (c <= '9')) {
400 formattingInfo =
401 new FormattingInfo(
402 formattingInfo.isLeftAligned(),
403 formattingInfo.isRightTruncated(),
404 formattingInfo.getMinLength(),
405 (formattingInfo.getMaxLength() * 10) + (c - '0'));
406 } else {
407 i = finalizeConverter(
408 c, pattern, i, currentLiteral, formattingInfo,
409 converterRegistry, rules, patternConverters, formattingInfos);
410 state = LITERAL_STATE;
411 formattingInfo = FormattingInfo.getDefault();
412 currentLiteral.setLength(0);
413 }
414
415 break;
416 }
417 }
418
419
420 if (currentLiteral.length() != 0) {
421 patternConverters.add(
422 new LiteralPatternConverter(currentLiteral.toString()));
423 formattingInfos.add(FormattingInfo.getDefault());
424 }
425 }
426
427 /***
428 * Creates a new PatternConverter.
429 *
430 *
431 * @param converterId converterId.
432 * @param currentLiteral literal to be used if converter is unrecognized or following converter
433 * if converterId contains extra characters.
434 * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
435 * @param rules map of stock pattern converters keyed by format specifier.
436 * @param options converter options.
437 * @return converter or null.
438 */
439 private static PatternConverter createConverter(
440 final String converterId, final StringBuffer currentLiteral,
441 final Map converterRegistry, final Map rules, final List options) {
442 String converterName = converterId;
443 Object converterObj = null;
444
445 for (int i = converterId.length(); (i > 0) && (converterObj == null);
446 i--) {
447 converterName = converterName.substring(0, i);
448
449 if (converterRegistry != null) {
450 converterObj = converterRegistry.get(converterName);
451 }
452
453 if ((converterObj == null) && (rules != null)) {
454 converterObj = rules.get(converterName);
455 }
456 }
457
458 if (converterObj == null) {
459 LogLog.error("Unrecognized format specifier [" + converterId + "]");
460
461 return null;
462 }
463
464 Class converterClass = null;
465
466 if (converterObj instanceof Class) {
467 converterClass = (Class) converterObj;
468 } else {
469 if (converterObj instanceof String) {
470 try {
471 converterClass = Loader.loadClass((String) converterObj);
472 } catch (ClassNotFoundException ex) {
473 LogLog.warn(
474 "Class for conversion pattern %" + converterName + " not found",
475 ex);
476
477 return null;
478 }
479 } else {
480 LogLog.warn(
481 "Bad map entry for conversion pattern %" + converterName + ".");
482
483 return null;
484 }
485 }
486
487 try {
488 Method factory =
489 converterClass.getMethod(
490 "newInstance",
491 new Class[] {
492 Class.forName("[Ljava.lang.String;")
493 });
494 String[] optionsArray = new String[options.size()];
495 optionsArray = (String[]) options.toArray(optionsArray);
496
497 Object newObj =
498 factory.invoke(null, new Object[] { optionsArray });
499
500 if (newObj instanceof PatternConverter) {
501 currentLiteral.delete(
502 0,
503 currentLiteral.length()
504 - (converterId.length() - converterName.length()));
505
506 return (PatternConverter) newObj;
507 } else {
508 LogLog.warn(
509 "Class " + converterClass.getName()
510 + " does not extend PatternConverter.");
511 }
512 } catch (Exception ex) {
513 LogLog.error("Error creating converter for " + converterId, ex);
514
515 try {
516
517
518 PatternConverter pc = (PatternConverter) converterClass.newInstance();
519 currentLiteral.delete(
520 0,
521 currentLiteral.length()
522 - (converterId.length() - converterName.length()));
523
524 return pc;
525 } catch (Exception ex2) {
526 LogLog.error("Error creating converter for " + converterId, ex2);
527 }
528 }
529
530 return null;
531 }
532
533 /***
534 * Processes a format specifier sequence.
535 *
536 * @param c initial character of format specifier.
537 * @param pattern conversion pattern
538 * @param i current position in conversion pattern.
539 * @param currentLiteral current literal.
540 * @param formattingInfo current field specifier.
541 * @param converterRegistry map of user-provided pattern converters keyed by format specifier, may be null.
542 * @param rules map of stock pattern converters keyed by format specifier.
543 * @param patternConverters list to receive parsed pattern converter.
544 * @param formattingInfos list to receive corresponding field specifier.
545 * @return position after format specifier sequence.
546 */
547 private static int finalizeConverter(
548 char c, String pattern, int i,
549 final StringBuffer currentLiteral, final FormattingInfo formattingInfo,
550 final Map converterRegistry, final Map rules, final List patternConverters,
551 final List formattingInfos) {
552 StringBuffer convBuf = new StringBuffer();
553 i = extractConverter(c, pattern, i, convBuf, currentLiteral);
554
555 String converterId = convBuf.toString();
556
557 List options = new ArrayList();
558 i = extractOptions(pattern, i, options);
559
560 PatternConverter pc =
561 createConverter(
562 converterId, currentLiteral, converterRegistry, rules, options);
563
564 if (pc == null) {
565 StringBuffer msg;
566
567 if ((converterId == null) || (converterId.length() == 0)) {
568 msg =
569 new StringBuffer("Empty conversion specifier starting at position ");
570 } else {
571 msg = new StringBuffer("Unrecognized conversion specifier [");
572 msg.append(converterId);
573 msg.append("] starting at position ");
574 }
575
576 msg.append(Integer.toString(i));
577 msg.append(" in conversion pattern.");
578
579 LogLog.error(msg.toString());
580
581 patternConverters.add(
582 new LiteralPatternConverter(currentLiteral.toString()));
583 formattingInfos.add(FormattingInfo.getDefault());
584 } else {
585 patternConverters.add(pc);
586 formattingInfos.add(formattingInfo);
587
588 if (currentLiteral.length() > 0) {
589 patternConverters.add(
590 new LiteralPatternConverter(currentLiteral.toString()));
591 formattingInfos.add(FormattingInfo.getDefault());
592 }
593 }
594
595 currentLiteral.setLength(0);
596
597 return i;
598 }
599
600 /***
601 * The class wraps another Map but throws exceptions on any attempt to modify the map.
602 */
603 private static class ReadOnlyMap implements Map {
604 /***
605 * Wrapped map.
606 */
607 private final Map map;
608
609 /***
610 * Constructor
611 * @param src source map.
612 */
613 public ReadOnlyMap(Map src) {
614 map = src;
615 }
616
617 /***
618 * {@inheritDoc}
619 */
620 public void clear() {
621 throw new UnsupportedOperationException();
622 }
623
624 /***
625 * {@inheritDoc}
626 */
627 public boolean containsKey(Object key) {
628 return map.containsKey(key);
629 }
630
631 /***
632 * {@inheritDoc}
633 */
634 public boolean containsValue(Object value) {
635 return map.containsValue(value);
636 }
637
638 /***
639 * {@inheritDoc}
640 */
641 public Set entrySet() {
642 return map.entrySet();
643 }
644
645 /***
646 * {@inheritDoc}
647 */
648 public Object get(Object key) {
649 return map.get(key);
650 }
651
652 /***
653 * {@inheritDoc}
654 */
655 public boolean isEmpty() {
656 return map.isEmpty();
657 }
658
659 /***
660 * {@inheritDoc}
661 */
662 public Set keySet() {
663 return map.keySet();
664 }
665
666 /***
667 * {@inheritDoc}
668 */
669 public Object put(Object key, Object value) {
670 throw new UnsupportedOperationException();
671 }
672
673 /***
674 * {@inheritDoc}
675 */
676 public void putAll(Map t) {
677 throw new UnsupportedOperationException();
678 }
679
680 /***
681 * {@inheritDoc}
682 */
683 public Object remove(Object key) {
684 throw new UnsupportedOperationException();
685 }
686
687 /***
688 * {@inheritDoc}
689 */
690 public int size() {
691 return map.size();
692 }
693
694 /***
695 * {@inheritDoc}
696 */
697 public Collection values() {
698 return map.values();
699 }
700 }
701 }