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