1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.pattern;
18
19 import java.lang.reflect.Method;
20 import java.lang.reflect.Modifier;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.apache.logging.log4j.Logger;
28 import org.apache.logging.log4j.core.config.Configuration;
29 import org.apache.logging.log4j.core.config.plugins.PluginManager;
30 import org.apache.logging.log4j.core.config.plugins.PluginType;
31 import org.apache.logging.log4j.status.StatusLogger;
32
33
34
35
36
37
38
39 public final class PatternParser {
40
41
42
43 private static final char ESCAPE_CHAR = '%';
44
45
46
47
48 private enum ParserState {
49
50
51
52 LITERAL_STATE,
53
54
55
56
57 CONVERTER_STATE,
58
59
60
61
62 DOT_STATE,
63
64
65
66
67 MIN_STATE,
68
69
70
71
72 MAX_STATE;
73 }
74
75 private static final Logger LOGGER = StatusLogger.getLogger();
76
77 private static final int BUF_SIZE = 32;
78
79 private static final int DECIMAL = 10;
80
81
82
83
84 private boolean handlesExceptions;
85
86 private final Configuration config;
87
88 private final Map<String, Class<PatternConverter>> converterRules;
89
90
91
92
93
94
95 public PatternParser(String converterKey) {
96 this(null, converterKey, null);
97 }
98
99
100
101
102
103
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
164
165
166
167
168
169
170
171
172
173
174
175
176
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
184
185
186
187
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
205
206
207
208
209
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
243
244
245
246
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
269 if (i == patternLength) {
270 currentLiteral.append(c);
271
272 continue;
273 }
274
275 if (c == ESCAPE_CHAR) {
276
277 switch (pattern.charAt(i)) {
278 case ESCAPE_CHAR:
279 currentLiteral.append(c);
280 i++;
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);
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
328 state = ParserState.LITERAL_STATE;
329 formattingInfo = FormattingInfo.getDefault();
330 currentLiteral.setLength(0);
331 }
332 }
333
334 break;
335
336 case MIN_STATE:
337 currentLiteral.append(c);
338
339 if ((c >= '0') && (c <= '9')) {
340
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
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 }
390 }
391
392
393 if (currentLiteral.length() != 0) {
394 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
395 formattingInfos.add(FormattingInfo.getDefault());
396 }
397 }
398
399
400
401
402
403
404
405
406
407
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
430
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
493
494
495
496
497
498
499
500
501
502
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 }