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 org.apache.logging.log4j.Logger;
20 import org.apache.logging.log4j.core.config.Configuration;
21 import org.apache.logging.log4j.core.config.plugins.PluginManager;
22 import org.apache.logging.log4j.core.config.plugins.PluginType;
23 import org.apache.logging.log4j.status.StatusLogger;
24
25 import java.lang.reflect.Method;
26 import java.lang.reflect.Modifier;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
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 private final Configuration config;
82
83 private final Map<String, Class<PatternConverter>> converterRules;
84
85
86
87
88
89
90 public PatternParser(final String converterKey) {
91 this(null, converterKey, null, null);
92 }
93
94
95
96
97
98
99
100 public PatternParser(final Configuration config, final String converterKey, final Class<?> expected) {
101 this(config, converterKey, expected, null);
102 }
103
104
105
106
107
108
109
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
181
182
183
184
185
186
187
188
189
190
191
192
193
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
201
202
203
204
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
222
223
224
225
226
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
260
261
262
263
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
286 if (i == patternLength) {
287 currentLiteral.append(c);
288
289 continue;
290 }
291
292 if (c == ESCAPE_CHAR) {
293
294 switch (pattern.charAt(i)) {
295 case ESCAPE_CHAR:
296 currentLiteral.append(c);
297 i++;
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);
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
345 state = ParserState.LITERAL_STATE;
346 formattingInfo = FormattingInfo.getDefault();
347 currentLiteral.setLength(0);
348 }
349 }
350
351 break;
352
353 case MIN_STATE:
354 currentLiteral.append(c);
355
356 if (c >= '0' && c <= '9') {
357
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
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 }
407 }
408
409
410 if (currentLiteral.length() != 0) {
411 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
412 formattingInfos.add(FormattingInfo.getDefault());
413 }
414 }
415
416
417
418
419
420
421
422
423
424
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
446
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
509
510
511
512
513
514
515
516
517
518
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 }