001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.log4j.pattern; 019 020import org.apache.log4j.helpers.Loader; 021import org.apache.log4j.helpers.LogLog; 022 023import java.lang.reflect.Method; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031// Contributors: Nelson Minar <(nelson@monkey.org> 032// Igor E. Poteryaev <jah@mail.ru> 033// Reinhard Deschler <reinhard.deschler@web.de> 034 035/** 036 * Most of the work of the {@link org.apache.log4j.EnhancedPatternLayout} class 037 * is delegated to the PatternParser class. 038 * <p>It is this class that parses conversion patterns and creates 039 * a chained list of {@link PatternConverter PatternConverters}. 040 * 041 * @author James P. Cakalic 042 * @author Ceki Gülcü 043 * @author Anders Kristensen 044 * @author Paul Smith 045 * @author Curt Arnold 046 * 047*/ 048public final class PatternParser { 049 /** 050 * Escape character for format specifier. 051 */ 052 private static final char ESCAPE_CHAR = '%'; 053 054 /** 055 * Literal state. 056 */ 057 private static final int LITERAL_STATE = 0; 058 059 /** 060 * In converter name state. 061 */ 062 private static final int CONVERTER_STATE = 1; 063 064 /** 065 * Dot state. 066 */ 067 private static final int DOT_STATE = 3; 068 069 /** 070 * Min state. 071 */ 072 private static final int MIN_STATE = 4; 073 074 /** 075 * Max state. 076 */ 077 private static final int MAX_STATE = 5; 078 079 /** 080 * Standard format specifiers for EnhancedPatternLayout. 081 */ 082 private static final Map PATTERN_LAYOUT_RULES; 083 084 /** 085 * Standard format specifiers for rolling file appenders. 086 */ 087 private static final Map FILENAME_PATTERN_RULES; 088 089 static { 090 // We set the global rules in the static initializer of PatternParser class 091 Map rules = new HashMap(17); 092 rules.put("c", LoggerPatternConverter.class); 093 rules.put("logger", LoggerPatternConverter.class); 094 095 rules.put("C", ClassNamePatternConverter.class); 096 rules.put("class", ClassNamePatternConverter.class); 097 098 rules.put("d", DatePatternConverter.class); 099 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 // 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)) { 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 //System.out.println("conv buffer is now ["+convBuf+"]."); 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 // In literal state, the last char is always a literal. 266 if (i == patternLength) { 267 currentLiteral.append(c); 268 269 continue; 270 } 271 272 if (c == ESCAPE_CHAR) { 273 // peek at the next char. 274 switch (pattern.charAt(i)) { 275 case ESCAPE_CHAR: 276 currentLiteral.append(c); 277 i++; // move pointer 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); // append % 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 // Next pattern is assumed to be a literal. 331 state = LITERAL_STATE; 332 formattingInfo = FormattingInfo.getDefault(); 333 currentLiteral.setLength(0); 334 } 335 } // switch 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 } // switch 399 } 400 401 // while 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 // try default constructor 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}