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 package org.apache.logging.log4j.core.pattern; 018 019 import java.lang.reflect.Method; 020 import java.lang.reflect.Modifier; 021 import java.util.ArrayList; 022 import java.util.HashMap; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Map; 026 027 import org.apache.logging.log4j.Logger; 028 import org.apache.logging.log4j.core.config.Configuration; 029 import org.apache.logging.log4j.core.config.plugins.PluginManager; 030 import org.apache.logging.log4j.core.config.plugins.PluginType; 031 import org.apache.logging.log4j.status.StatusLogger; 032 033 /** 034 * Most of the work of the {@link org.apache.logging.log4j.core.layout.PatternLayout} class 035 * is delegated to the PatternParser class. 036 * <p>It is this class that parses conversion patterns and creates 037 * a chained list of {@link PatternConverter PatternConverters}. 038 */ 039 public final class PatternParser { 040 /** 041 * Escape character for format specifier. 042 */ 043 private static final char ESCAPE_CHAR = '%'; 044 045 /** 046 * The states the parser can be in while parsing the pattern. 047 */ 048 private enum ParserState { 049 /** 050 * Literal state. 051 */ 052 LITERAL_STATE, 053 054 /** 055 * In converter name state. 056 */ 057 CONVERTER_STATE, 058 059 /** 060 * Dot state. 061 */ 062 DOT_STATE, 063 064 /** 065 * Min state. 066 */ 067 MIN_STATE, 068 069 /** 070 * Max state. 071 */ 072 MAX_STATE; 073 } 074 075 private static final Logger LOGGER = StatusLogger.getLogger(); 076 077 private static final int BUF_SIZE = 32; 078 079 private static final int DECIMAL = 10; 080 081 private final Configuration config; 082 083 private final Map<String, Class<PatternConverter>> converterRules; 084 085 086 /** 087 * Constructor. 088 * @param converterKey The type of converters that will be used. 089 */ 090 public PatternParser(final String converterKey) { 091 this(null, converterKey, null, null); 092 } 093 094 /** 095 * Constructor. 096 * @param config The current Configuration. 097 * @param converterKey The key to lookup the converters. 098 * @param expected The expected base Class of each Converter. 099 */ 100 public PatternParser(final Configuration config, final String converterKey, final Class<?> expected) { 101 this(config, converterKey, expected, null); 102 } 103 104 /** 105 * Constructor. 106 * @param config The current Configuration. 107 * @param converterKey The key to lookup the converters. 108 * @param expectedClass The expected base Class of each Converter. 109 * @param filterClass Filter the returned plugins after calling the plugin manager. 110 */ 111 public PatternParser(final Configuration config, final String converterKey, final Class<?> expectedClass, 112 final Class<?> filterClass) { 113 this.config = config; 114 final PluginManager manager = new PluginManager(converterKey, expectedClass); 115 manager.collectPlugins(); 116 final Map<String, PluginType> plugins = manager.getPlugins(); 117 final Map<String, Class<PatternConverter>> converters = new HashMap<String, Class<PatternConverter>>(); 118 119 for (final PluginType type : plugins.values()) { 120 try { 121 final Class<PatternConverter> clazz = type.getPluginClass(); 122 if (filterClass != null && !filterClass.isAssignableFrom(clazz)) { 123 continue; 124 } 125 final ConverterKeys keys = clazz.getAnnotation(ConverterKeys.class); 126 if (keys != null) { 127 for (final String key : keys.value()) { 128 converters.put(key, clazz); 129 } 130 } 131 } catch (final Exception ex) { 132 LOGGER.error("Error processing plugin " + type.getElementName(), ex); 133 } 134 } 135 converterRules = converters; 136 } 137 138 139 public List<PatternFormatter> parse(final String pattern) { 140 return parse(pattern, false); 141 } 142 143 144 public List<PatternFormatter> parse(final String pattern, final boolean handleExceptions) { 145 final List<PatternFormatter> list = new ArrayList<PatternFormatter>(); 146 final List<PatternConverter> converters = new ArrayList<PatternConverter>(); 147 final List<FormattingInfo> fields = new ArrayList<FormattingInfo>(); 148 149 parse(pattern, converters, fields); 150 151 final Iterator<FormattingInfo> fieldIter = fields.iterator(); 152 boolean handlesExceptions = false; 153 154 for (final PatternConverter converter : converters) { 155 LogEventPatternConverter pc; 156 if (converter instanceof LogEventPatternConverter) { 157 pc = (LogEventPatternConverter) converter; 158 handlesExceptions |= pc.handlesThrowable(); 159 } else { 160 pc = new LiteralPatternConverter(config, ""); 161 } 162 163 FormattingInfo field; 164 if (fieldIter.hasNext()) { 165 field = fieldIter.next(); 166 } else { 167 field = FormattingInfo.getDefault(); 168 } 169 list.add(new PatternFormatter(pc, field)); 170 } 171 if (handleExceptions && !handlesExceptions) { 172 final LogEventPatternConverter pc = ExtendedThrowablePatternConverter.newInstance(null); 173 list.add(new PatternFormatter(pc, FormattingInfo.getDefault())); 174 } 175 return list; 176 } 177 178 /** 179 * Extract the converter identifier found at position i. 180 * <p/> 181 * After this function returns, the variable i will point to the 182 * first char after the end of the converter identifier. 183 * <p/> 184 * If i points to a char which is not a character acceptable at the 185 * start of a unicode identifier, the value null is returned. 186 * 187 * @param lastChar last processed character. 188 * @param pattern format string. 189 * @param i current index into pattern format. 190 * @param convBuf buffer to receive conversion specifier. 191 * @param currentLiteral literal to be output in case format specifier in unrecognized. 192 * @return position in pattern after converter. 193 */ 194 private static int extractConverter( 195 final char lastChar, final String pattern, int i, final StringBuilder convBuf, 196 final StringBuilder currentLiteral) { 197 convBuf.setLength(0); 198 199 // When this method is called, lastChar points to the first character of the 200 // conversion word. For example: 201 // For "%hello" lastChar = 'h' 202 // For "%-5hello" lastChar = 'h' 203 //System.out.println("lastchar is "+lastChar); 204 if (!Character.isUnicodeIdentifierStart(lastChar)) { 205 return i; 206 } 207 208 convBuf.append(lastChar); 209 210 while (i < pattern.length() && Character.isUnicodeIdentifierPart(pattern.charAt(i))) { 211 convBuf.append(pattern.charAt(i)); 212 currentLiteral.append(pattern.charAt(i)); 213 i++; 214 } 215 216 return i; 217 } 218 219 /** 220 * Extract options. 221 * 222 * @param pattern conversion pattern. 223 * @param i start of options. 224 * @param options array to receive extracted options 225 * @return position in pattern after options. 226 */ 227 private static int extractOptions(final String pattern, int i, final List<String> options) { 228 while (i < pattern.length() && pattern.charAt(i) == '{') { 229 final int begin = i++; 230 int end; 231 int depth = 0; 232 do { 233 end = pattern.indexOf('}', i); 234 if (end != -1) { 235 final int next = pattern.indexOf("{", i); 236 if (next != -1 && next < end) { 237 i = end + 1; 238 ++depth; 239 } else if (depth > 0) { 240 --depth; 241 } 242 } 243 } while (depth > 0); 244 245 if (end == -1) { 246 break; 247 } 248 249 final String r = pattern.substring(begin + 1, end); 250 options.add(r); 251 i = end + 1; 252 } 253 254 return i; 255 } 256 257 /** 258 * Parse a format specifier. 259 * 260 * @param pattern pattern to parse. 261 * @param patternConverters list to receive pattern converters. 262 * @param formattingInfos list to receive field specifiers corresponding to pattern converters. 263 */ 264 public void parse(final String pattern, final List<PatternConverter> patternConverters, 265 final List<FormattingInfo> formattingInfos) { 266 if (pattern == null) { 267 throw new NullPointerException("pattern"); 268 } 269 270 final StringBuilder currentLiteral = new StringBuilder(BUF_SIZE); 271 272 final int patternLength = pattern.length(); 273 ParserState state = ParserState.LITERAL_STATE; 274 char c; 275 int i = 0; 276 FormattingInfo formattingInfo = FormattingInfo.getDefault(); 277 278 while (i < patternLength) { 279 c = pattern.charAt(i++); 280 281 switch (state) { 282 case LITERAL_STATE: 283 284 // In literal state, the last char is always a literal. 285 if (i == patternLength) { 286 currentLiteral.append(c); 287 288 continue; 289 } 290 291 if (c == ESCAPE_CHAR) { 292 // peek at the next char. 293 switch (pattern.charAt(i)) { 294 case ESCAPE_CHAR: 295 currentLiteral.append(c); 296 i++; // move pointer 297 298 break; 299 300 default: 301 302 if (currentLiteral.length() != 0) { 303 patternConverters.add(new LiteralPatternConverter(config, 304 currentLiteral.toString())); 305 formattingInfos.add(FormattingInfo.getDefault()); 306 } 307 308 currentLiteral.setLength(0); 309 currentLiteral.append(c); // append % 310 state = ParserState.CONVERTER_STATE; 311 formattingInfo = FormattingInfo.getDefault(); 312 } 313 } else { 314 currentLiteral.append(c); 315 } 316 317 break; 318 319 case CONVERTER_STATE: 320 currentLiteral.append(c); 321 322 switch (c) { 323 case '-': 324 formattingInfo = 325 new FormattingInfo(true, formattingInfo.getMinLength(), 326 formattingInfo.getMaxLength()); 327 break; 328 329 case '.': 330 state = ParserState.DOT_STATE; 331 break; 332 333 default: 334 335 if (c >= '0' && c <= '9') { 336 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), c - '0', 337 formattingInfo.getMaxLength()); 338 state = ParserState.MIN_STATE; 339 } else { 340 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, 341 converterRules, patternConverters, formattingInfos); 342 343 // Next pattern is assumed to be a literal. 344 state = ParserState.LITERAL_STATE; 345 formattingInfo = FormattingInfo.getDefault(); 346 currentLiteral.setLength(0); 347 } 348 } // switch 349 350 break; 351 352 case MIN_STATE: 353 currentLiteral.append(c); 354 355 if (c >= '0' && c <= '9') { 356 // Multiply the existing value and add the value of the number just encountered. 357 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), 358 formattingInfo.getMinLength() * DECIMAL + c - '0', 359 formattingInfo.getMaxLength()); 360 } else if (c == '.') { 361 state = ParserState.DOT_STATE; 362 } else { 363 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, 364 converterRules, patternConverters, formattingInfos); 365 state = ParserState.LITERAL_STATE; 366 formattingInfo = FormattingInfo.getDefault(); 367 currentLiteral.setLength(0); 368 } 369 370 break; 371 372 case DOT_STATE: 373 currentLiteral.append(c); 374 375 if (c >= '0' && c <= '9') { 376 formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), 377 formattingInfo.getMinLength(), c - '0'); 378 state = ParserState.MAX_STATE; 379 } else { 380 LOGGER.error("Error occurred in position " + i 381 + ".\n Was expecting digit, instead got char \"" + c + "\"."); 382 383 state = ParserState.LITERAL_STATE; 384 } 385 386 break; 387 388 case MAX_STATE: 389 currentLiteral.append(c); 390 391 if (c >= '0' && c <= '9') { 392 // Multiply the existing value and add the value of the number just encountered. 393 formattingInfo = new FormattingInfo( 394 formattingInfo.isLeftAligned(), formattingInfo.getMinLength(), 395 formattingInfo.getMaxLength() * DECIMAL + c - '0'); 396 } else { 397 i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, 398 converterRules, patternConverters, formattingInfos); 399 state = ParserState.LITERAL_STATE; 400 formattingInfo = FormattingInfo.getDefault(); 401 currentLiteral.setLength(0); 402 } 403 404 break; 405 } // switch 406 } 407 408 // while 409 if (currentLiteral.length() != 0) { 410 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString())); 411 formattingInfos.add(FormattingInfo.getDefault()); 412 } 413 } 414 415 /** 416 * Creates a new PatternConverter. 417 * 418 * @param converterId converterId. 419 * @param currentLiteral literal to be used if converter is unrecognized or following converter 420 * if converterId contains extra characters. 421 * @param rules map of stock pattern converters keyed by format specifier. 422 * @param options converter options. 423 * @return converter or null. 424 */ 425 private PatternConverter createConverter(final String converterId, final StringBuilder currentLiteral, 426 final Map<String, Class<PatternConverter>> rules, 427 final List<String> options) { 428 String converterName = converterId; 429 Class<PatternConverter> converterClass = null; 430 431 for (int i = converterId.length(); i > 0 && converterClass == null; i--) { 432 converterName = converterName.substring(0, i); 433 434 if (converterClass == null && rules != null) { 435 converterClass = rules.get(converterName); 436 } 437 } 438 439 if (converterClass == null) { 440 LOGGER.error("Unrecognized format specifier [" + converterId + "]"); 441 return null; 442 } 443 444 // Work around the regression bug in Class.getDeclaredMethods() in Oracle Java in version > 1.6.0_17: 445 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6815786 446 final Method[] methods = converterClass.getDeclaredMethods(); 447 Method newInstanceMethod = null; 448 for (final Method method : methods) { 449 if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(converterClass) && 450 method.getName().equals("newInstance")) { 451 if (newInstanceMethod == null) { 452 newInstanceMethod = method; 453 } else if (method.getReturnType().equals(newInstanceMethod.getReturnType())) { 454 LOGGER.error("Class " + converterClass + " cannot contain multiple static newInstance methods"); 455 return null; 456 } 457 } 458 } 459 if (newInstanceMethod == null) { 460 LOGGER.error("Class " + converterClass + " does not contain a static newInstance method"); 461 return null; 462 } 463 464 final Class<?>[] parmTypes = newInstanceMethod.getParameterTypes(); 465 final Object [] parms = parmTypes.length > 0 ? new Object[parmTypes.length] : null; 466 467 if (parms != null) { 468 int i = 0; 469 boolean errors = false; 470 for (final Class<?> clazz : parmTypes) { 471 if (clazz.isArray() && clazz.getName().equals("[Ljava.lang.String;")) { 472 final String[] optionsArray = options.toArray(new String[options.size()]); 473 parms[i] = optionsArray; 474 } else if (clazz.isAssignableFrom(Configuration.class)) { 475 parms[i] = config; 476 } else { 477 LOGGER.error("Unknown parameter type " + clazz.getName() + " for static newInstance method of " + 478 converterClass.getName()); 479 errors = true; 480 } 481 ++i; 482 } 483 if (errors) { 484 return null; 485 } 486 } 487 488 try { 489 final Object newObj = newInstanceMethod.invoke(null, parms); 490 491 if (newObj instanceof PatternConverter) { 492 currentLiteral.delete(0, currentLiteral.length() 493 - (converterId.length() - converterName.length())); 494 495 return (PatternConverter) newObj; 496 } else { 497 LOGGER.warn("Class " + converterClass.getName() + " does not extend PatternConverter."); 498 } 499 } catch (final Exception ex) { 500 LOGGER.error("Error creating converter for " + converterId, ex); 501 } 502 503 return null; 504 } 505 506 /** 507 * Processes a format specifier sequence. 508 * 509 * @param c initial character of format specifier. 510 * @param pattern conversion pattern 511 * @param i current position in conversion pattern. 512 * @param currentLiteral current literal. 513 * @param formattingInfo current field specifier. 514 * @param rules map of stock pattern converters keyed by format specifier. 515 * @param patternConverters list to receive parsed pattern converter. 516 * @param formattingInfos list to receive corresponding field specifier. 517 * @return position after format specifier sequence. 518 */ 519 private int finalizeConverter(final char c, final String pattern, int i, 520 final StringBuilder currentLiteral, final FormattingInfo formattingInfo, 521 final Map<String, Class<PatternConverter>> rules, 522 final List<PatternConverter> patternConverters, final List<FormattingInfo> formattingInfos) { 523 final StringBuilder convBuf = new StringBuilder(); 524 i = extractConverter(c, pattern, i, convBuf, currentLiteral); 525 526 final String converterId = convBuf.toString(); 527 528 final List<String> options = new ArrayList<String>(); 529 i = extractOptions(pattern, i, options); 530 531 final PatternConverter pc = createConverter(converterId, currentLiteral, rules, options); 532 533 if (pc == null) { 534 StringBuilder msg; 535 536 if (converterId == null || converterId.length() == 0) { 537 msg = 538 new StringBuilder("Empty conversion specifier starting at position "); 539 } else { 540 msg = new StringBuilder("Unrecognized conversion specifier ["); 541 msg.append(converterId); 542 msg.append("] starting at position "); 543 } 544 545 msg.append(Integer.toString(i)); 546 msg.append(" in conversion pattern."); 547 548 LOGGER.error(msg.toString()); 549 550 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString())); 551 formattingInfos.add(FormattingInfo.getDefault()); 552 } else { 553 patternConverters.add(pc); 554 formattingInfos.add(formattingInfo); 555 556 if (currentLiteral.length() > 0) { 557 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString())); 558 formattingInfos.add(FormattingInfo.getDefault()); 559 } 560 } 561 562 currentLiteral.setLength(0); 563 564 return i; 565 } 566 }