2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package org.apache.log4j.pattern;
20 import org.apache.log4j.helpers.Loader;
21 import org.apache.log4j.helpers.LogLog;
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;
31 // Contributors: Nelson Minar <(nelson@monkey.org>
32 // Igor E. Poteryaev <jah@mail.ru>
33 // Reinhard Deschler <reinhard.deschler@web.de>
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}.
41 * @author James P. Cakalic
42 * @author Ceki Gülcü
43 * @author Anders Kristensen
48 public final class PatternParser {
50 * Escape character for format specifier.
52 private static final char ESCAPE_CHAR = '%';
57 private static final int LITERAL_STATE = 0;
60 * In converter name state.
62 private static final int CONVERTER_STATE = 1;
67 private static final int DOT_STATE = 3;
72 private static final int MIN_STATE = 4;
77 private static final int MAX_STATE = 5;
80 * Standard format specifiers for EnhancedPatternLayout.
82 private static final Map PATTERN_LAYOUT_RULES;
85 * Standard format specifiers for rolling file appenders.
87 private static final Map FILENAME_PATTERN_RULES;
90 // We set the global rules in the static initializer of PatternParser class
91 Map rules = new HashMap(17);
92 rules.put("c", LoggerPatternConverter.class);
93 rules.put("logger", LoggerPatternConverter.class);
95 rules.put("C", ClassNamePatternConverter.class);
96 rules.put("class", ClassNamePatternConverter.class);
98 rules.put("d", DatePatternConverter.class);
99 rules.put("date", DatePatternConverter.class);
101 rules.put("F", FileLocationPatternConverter.class);
102 rules.put("file", FileLocationPatternConverter.class);
104 rules.put("l", FullLocationPatternConverter.class);
106 rules.put("L", LineLocationPatternConverter.class);
107 rules.put("line", LineLocationPatternConverter.class);
109 rules.put("m", MessagePatternConverter.class);
110 rules.put("message", MessagePatternConverter.class);
112 rules.put("n", LineSeparatorPatternConverter.class);
114 rules.put("M", MethodLocationPatternConverter.class);
115 rules.put("method", MethodLocationPatternConverter.class);
117 rules.put("p", LevelPatternConverter.class);
118 rules.put("level", LevelPatternConverter.class);
120 rules.put("r", RelativeTimePatternConverter.class);
121 rules.put("relative", RelativeTimePatternConverter.class);
123 rules.put("t", ThreadPatternConverter.class);
124 rules.put("thread", ThreadPatternConverter.class);
126 rules.put("x", NDCPatternConverter.class);
127 rules.put("ndc", NDCPatternConverter.class);
129 rules.put("X", PropertiesPatternConverter.class);
130 rules.put("properties", PropertiesPatternConverter.class);
132 rules.put("sn", SequenceNumberPatternConverter.class);
133 rules.put("sequenceNumber", SequenceNumberPatternConverter.class);
135 rules.put("throwable", ThrowableInformationPatternConverter.class);
136 PATTERN_LAYOUT_RULES = new ReadOnlyMap(rules);
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);
144 FILENAME_PATTERN_RULES = new ReadOnlyMap(fnameRules);
148 * Private constructor.
150 private PatternParser() {
154 * Get standard format specifiers for EnhancedPatternLayout.
155 * @return read-only map of format converter classes keyed by format specifier strings.
157 public static Map getPatternLayoutRules() {
158 return PATTERN_LAYOUT_RULES;
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.
165 public static Map getFileNamePatternRules() {
166 return FILENAME_PATTERN_RULES;
169 /** Extract the converter identifier found at position i.
171 * After this function returns, the variable i will point to the
172 * first char after the end of the converter identifier.
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.
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.
184 private static int extractConverter(
185 char lastChar, final String pattern, int i, final StringBuffer convBuf,
186 final StringBuffer currentLiteral) {
187 convBuf.setLength(0);
189 // When this method is called, lastChar points to the first character of the
190 // conversion word. For example:
191 // For "%hello" lastChar = 'h'
192 // For "%-5hello" lastChar = 'h'
193 //System.out.println("lastchar is "+lastChar);
194 if (!Character.isUnicodeIdentifierStart(lastChar)) {
198 convBuf.append(lastChar);
201 (i < pattern.length())
202 && Character.isUnicodeIdentifierPart(pattern.charAt(i))) {
203 convBuf.append(pattern.charAt(i));
204 currentLiteral.append(pattern.charAt(i));
206 //System.out.println("conv buffer is now ["+convBuf+"].");
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.
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);
228 String r = pattern.substring(i + 1, end);
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.
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");
251 StringBuffer currentLiteral = new StringBuffer(32);
253 int patternLength = pattern.length();
254 int state = LITERAL_STATE;
257 FormattingInfo formattingInfo = FormattingInfo.getDefault();
259 while (i < patternLength) {
260 c = pattern.charAt(i++);
265 // In literal state, the last char is always a literal.
266 if (i == patternLength) {
267 currentLiteral.append(c);
272 if (c == ESCAPE_CHAR) {
273 // peek at the next char.
274 switch (pattern.charAt(i)) {
276 currentLiteral.append(c);
283 if (currentLiteral.length() != 0) {
284 patternConverters.add(
285 new LiteralPatternConverter(currentLiteral.toString()));
286 formattingInfos.add(FormattingInfo.getDefault());
289 currentLiteral.setLength(0);
290 currentLiteral.append(c); // append %
291 state = CONVERTER_STATE;
292 formattingInfo = FormattingInfo.getDefault();
295 currentLiteral.append(c);
300 case CONVERTER_STATE:
301 currentLiteral.append(c);
308 formattingInfo.isRightTruncated(),
309 formattingInfo.getMinLength(),
310 formattingInfo.getMaxLength());
316 formattingInfo.isLeftAligned(),
318 formattingInfo.getMinLength(),
319 formattingInfo.getMaxLength());
330 if ((c >= '0') && (c <= '9')) {
333 formattingInfo.isLeftAligned(),
334 formattingInfo.isRightTruncated(),
336 formattingInfo.getMaxLength());
339 i = finalizeConverter(
340 c, pattern, i, currentLiteral, formattingInfo,
341 converterRegistry, rules, patternConverters, formattingInfos);
343 // Next pattern is assumed to be a literal.
344 state = LITERAL_STATE;
345 formattingInfo = FormattingInfo.getDefault();
346 currentLiteral.setLength(0);
353 currentLiteral.append(c);
355 if ((c >= '0') && (c <= '9')) {
358 formattingInfo.isLeftAligned(),
359 formattingInfo.isRightTruncated(),
360 (formattingInfo.getMinLength() * 10) + (c - '0'),
361 formattingInfo.getMaxLength());
362 } else if (c == '.') {
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);
376 currentLiteral.append(c);
378 if ((c >= '0') && (c <= '9')) {
381 formattingInfo.isLeftAligned(),
382 formattingInfo.isRightTruncated(),
383 formattingInfo.getMinLength(),
388 "Error occured in position " + i
389 + ".\n Was expecting digit, instead got char \"" + c + "\".");
391 state = LITERAL_STATE;
397 currentLiteral.append(c);
399 if ((c >= '0') && (c <= '9')) {
402 formattingInfo.isLeftAligned(),
403 formattingInfo.isRightTruncated(),
404 formattingInfo.getMinLength(),
405 (formattingInfo.getMaxLength() * 10) + (c - '0'));
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);
420 if (currentLiteral.length() != 0) {
421 patternConverters.add(
422 new LiteralPatternConverter(currentLiteral.toString()));
423 formattingInfos.add(FormattingInfo.getDefault());
428 * Creates a new PatternConverter.
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.
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;
445 for (int i = converterId.length(); (i > 0) && (converterObj == null);
447 converterName = converterName.substring(0, i);
449 if (converterRegistry != null) {
450 converterObj = converterRegistry.get(converterName);
453 if ((converterObj == null) && (rules != null)) {
454 converterObj = rules.get(converterName);
458 if (converterObj == null) {
459 LogLog.error("Unrecognized format specifier [" + converterId + "]");
464 Class converterClass = null;
466 if (converterObj instanceof Class) {
467 converterClass = (Class) converterObj;
469 if (converterObj instanceof String) {
471 converterClass = Loader.loadClass((String) converterObj);
472 } catch (ClassNotFoundException ex) {
474 "Class for conversion pattern %" + converterName + " not found",
481 "Bad map entry for conversion pattern %" + converterName + ".");
489 converterClass.getMethod(
492 Class.forName("[Ljava.lang.String;")
494 String[] optionsArray = new String[options.size()];
495 optionsArray = (String[]) options.toArray(optionsArray);
498 factory.invoke(null, new Object[] { optionsArray });
500 if (newObj instanceof PatternConverter) {
501 currentLiteral.delete(
503 currentLiteral.length()
504 - (converterId.length() - converterName.length()));
506 return (PatternConverter) newObj;
509 "Class " + converterClass.getName()
510 + " does not extend PatternConverter.");
512 } catch (Exception ex) {
513 LogLog.error("Error creating converter for " + converterId, ex);
517 // try default constructor
518 PatternConverter pc = (PatternConverter) converterClass.newInstance();
519 currentLiteral.delete(
521 currentLiteral.length()
522 - (converterId.length() - converterName.length()));
525 } catch (Exception ex2) {
526 LogLog.error("Error creating converter for " + converterId, ex2);
534 * Processes a format specifier sequence.
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.
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);
555 String converterId = convBuf.toString();
557 List options = new ArrayList();
558 i = extractOptions(pattern, i, options);
560 PatternConverter pc =
562 converterId, currentLiteral, converterRegistry, rules, options);
567 if ((converterId == null) || (converterId.length() == 0)) {
569 new StringBuffer("Empty conversion specifier starting at position ");
571 msg = new StringBuffer("Unrecognized conversion specifier [");
572 msg.append(converterId);
573 msg.append("] starting at position ");
576 msg.append(Integer.toString(i));
577 msg.append(" in conversion pattern.");
579 LogLog.error(msg.toString());
581 patternConverters.add(
582 new LiteralPatternConverter(currentLiteral.toString()));
583 formattingInfos.add(FormattingInfo.getDefault());
585 patternConverters.add(pc);
586 formattingInfos.add(formattingInfo);
588 if (currentLiteral.length() > 0) {
589 patternConverters.add(
590 new LiteralPatternConverter(currentLiteral.toString()));
591 formattingInfos.add(FormattingInfo.getDefault());
595 currentLiteral.setLength(0);
601 * The class wraps another Map but throws exceptions on any attempt to modify the map.
603 private static class ReadOnlyMap implements Map {
607 private final Map map;
611 * @param src source map.
613 public ReadOnlyMap(Map src) {
620 public void clear() {
621 throw new UnsupportedOperationException();
627 public boolean containsKey(Object key) {
628 return map.containsKey(key);
634 public boolean containsValue(Object value) {
635 return map.containsValue(value);
641 public Set entrySet() {
642 return map.entrySet();
648 public Object get(Object key) {
655 public boolean isEmpty() {
656 return map.isEmpty();
662 public Set keySet() {
669 public Object put(Object key, Object value) {
670 throw new UnsupportedOperationException();
676 public void putAll(Map t) {
677 throw new UnsupportedOperationException();
683 public Object remove(Object key) {
684 throw new UnsupportedOperationException();
697 public Collection values() {