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.core.helpers.Strings;
32 import org.apache.logging.log4j.status.StatusLogger;
33
34
35
36
37
38
39
40 public final class PatternParser {
41
42
43
44 private static final char ESCAPE_CHAR = '%';
45
46
47
48
49 private enum ParserState {
50
51
52
53 LITERAL_STATE,
54
55
56
57
58 CONVERTER_STATE,
59
60
61
62
63 DOT_STATE,
64
65
66
67
68 MIN_STATE,
69
70
71
72
73 MAX_STATE;
74 }
75
76 private static final Logger LOGGER = StatusLogger.getLogger();
77
78 private static final int BUF_SIZE = 32;
79
80 private static final int DECIMAL = 10;
81
82 private final Configuration config;
83
84 private final Map<String, Class<PatternConverter>> converterRules;
85
86
87
88
89
90
91 public PatternParser(final String converterKey) {
92 this(null, converterKey, null, null);
93 }
94
95
96
97
98
99
100
101 public PatternParser(final Configuration config, final String converterKey, final Class<?> expected) {
102 this(config, converterKey, expected, null);
103 }
104
105
106
107
108
109
110
111
112 public PatternParser(final Configuration config, final String converterKey, final Class<?> expectedClass,
113 final Class<?> filterClass) {
114 this.config = config;
115 final PluginManager manager = new PluginManager(converterKey, expectedClass);
116 manager.collectPlugins();
117 final Map<String, PluginType<?>> plugins = manager.getPlugins();
118 final Map<String, Class<PatternConverter>> converters = new HashMap<String, Class<PatternConverter>>();
119
120 for (final PluginType<?> type : plugins.values()) {
121 try {
122 @SuppressWarnings("unchecked")
123 final Class<PatternConverter> clazz = (Class<PatternConverter>)type.getPluginClass();
124 if (filterClass != null && !filterClass.isAssignableFrom(clazz)) {
125 continue;
126 }
127 final ConverterKeys keys = clazz.getAnnotation(ConverterKeys.class);
128 if (keys != null) {
129 for (final String key : keys.value()) {
130 converters.put(key, clazz);
131 }
132 }
133 } catch (final Exception ex) {
134 LOGGER.error("Error processing plugin " + type.getElementName(), ex);
135 }
136 }
137 converterRules = converters;
138 }
139
140 public List<PatternFormatter> parse(final String pattern) {
141 return parse(pattern, false);
142 }
143
144 public List<PatternFormatter> parse(final String pattern, final boolean alwaysWriteExceptions) {
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 handlesThrowable = false;
153
154 for (final PatternConverter converter : converters) {
155 LogEventPatternConverter pc;
156 if (converter instanceof LogEventPatternConverter) {
157 pc = (LogEventPatternConverter) converter;
158 handlesThrowable |= 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 (alwaysWriteExceptions && !handlesThrowable) {
172 final LogEventPatternConverter pc = ExtendedThrowablePatternConverter.newInstance(null);
173 list.add(new PatternFormatter(pc, FormattingInfo.getDefault()));
174 }
175 return list;
176 }
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
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
200
201
202
203
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
221
222
223
224
225
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
259
260
261
262
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
285 if (i == patternLength) {
286 currentLiteral.append(c);
287
288 continue;
289 }
290
291 if (c == ESCAPE_CHAR) {
292
293 switch (pattern.charAt(i)) {
294 case ESCAPE_CHAR:
295 currentLiteral.append(c);
296 i++;
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);
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
344 state = ParserState.LITERAL_STATE;
345 formattingInfo = FormattingInfo.getDefault();
346 currentLiteral.setLength(0);
347 }
348 }
349
350 break;
351
352 case MIN_STATE:
353 currentLiteral.append(c);
354
355 if (c >= '0' && c <= '9') {
356
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
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 }
406 }
407
408
409 if (currentLiteral.length() != 0) {
410 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
411 formattingInfos.add(FormattingInfo.getDefault());
412 }
413 }
414
415
416
417
418
419
420
421
422
423
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
445
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
508
509
510
511
512
513
514
515
516
517
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 (Strings.isEmpty(converterId)) {
537 msg = new StringBuilder("Empty conversion specifier starting at position ");
538 } else {
539 msg = new StringBuilder("Unrecognized conversion specifier [");
540 msg.append(converterId);
541 msg.append("] starting at position ");
542 }
543
544 msg.append(Integer.toString(i));
545 msg.append(" in conversion pattern.");
546
547 LOGGER.error(msg.toString());
548
549 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
550 formattingInfos.add(FormattingInfo.getDefault());
551 } else {
552 patternConverters.add(pc);
553 formattingInfos.add(formattingInfo);
554
555 if (currentLiteral.length() > 0) {
556 patternConverters.add(new LiteralPatternConverter(config, currentLiteral.toString()));
557 formattingInfos.add(FormattingInfo.getDefault());
558 }
559 }
560
561 currentLiteral.setLength(0);
562
563 return i;
564 }
565 }