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