JAL-3048 test updated for AlignExportSettings changes
[jalview.git] / srcjar / org / apache / log4j / pattern / PatternParser.java
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package org.apache.log4j.pattern;
19
20 import org.apache.log4j.helpers.Loader;
21 import org.apache.log4j.helpers.LogLog;
22
23 import java.lang.reflect.Method;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30
31 // Contributors:   Nelson Minar <(nelson@monkey.org>
32 //                 Igor E. Poteryaev <jah@mail.ru>
33 //                 Reinhard Deschler <reinhard.deschler@web.de>
34
35 /**
36  * Most of the work of the {@link org.apache.log4j.EnhancedPatternLayout} class
37  * is delegated to the PatternParser class.
38  * <p>It is this class that parses conversion patterns and creates
39  * a chained list of {@link PatternConverter PatternConverters}.
40  *
41  * @author James P. Cakalic
42  * @author Ceki G&uuml;lc&uuml;
43  * @author Anders Kristensen
44  * @author Paul Smith
45  * @author Curt Arnold
46  *
47 */
48 public final class PatternParser {
49   /**
50    * Escape character for format specifier.
51    */
52   private static final char ESCAPE_CHAR = '%';
53
54   /**
55    * Literal state.
56    */
57   private static final int LITERAL_STATE = 0;
58
59   /**
60    * In converter name state.
61    */
62   private static final int CONVERTER_STATE = 1;
63
64   /**
65    * Dot state.
66    */
67   private static final int DOT_STATE = 3;
68
69   /**
70    * Min state.
71    */
72   private static final int MIN_STATE = 4;
73
74   /**
75    * Max state.
76    */
77   private static final int MAX_STATE = 5;
78
79   /**
80    * Standard format specifiers for EnhancedPatternLayout.
81    */
82   private static final Map PATTERN_LAYOUT_RULES;
83
84   /**
85    * Standard format specifiers for rolling file appenders.
86    */
87   private static final Map FILENAME_PATTERN_RULES;
88
89   static {
90     // We set the global rules in the static initializer of PatternParser class
91     Map rules = new HashMap(17);
92     rules.put("c", LoggerPatternConverter.class);
93     rules.put("logger", LoggerPatternConverter.class);
94
95     rules.put("C", ClassNamePatternConverter.class);
96     rules.put("class", ClassNamePatternConverter.class);
97
98     rules.put("d", DatePatternConverter.class);
99     rules.put("date", DatePatternConverter.class);
100
101     rules.put("F", FileLocationPatternConverter.class);
102     rules.put("file", FileLocationPatternConverter.class);
103
104     rules.put("l", FullLocationPatternConverter.class);
105
106     rules.put("L", LineLocationPatternConverter.class);
107     rules.put("line", LineLocationPatternConverter.class);
108
109     rules.put("m", MessagePatternConverter.class);
110     rules.put("message", MessagePatternConverter.class);
111
112     rules.put("n", LineSeparatorPatternConverter.class);
113
114     rules.put("M", MethodLocationPatternConverter.class);
115     rules.put("method", MethodLocationPatternConverter.class);
116
117     rules.put("p", LevelPatternConverter.class);
118     rules.put("level", LevelPatternConverter.class);
119
120     rules.put("r", RelativeTimePatternConverter.class);
121     rules.put("relative", RelativeTimePatternConverter.class);
122
123     rules.put("t", ThreadPatternConverter.class);
124     rules.put("thread", ThreadPatternConverter.class);
125
126     rules.put("x", NDCPatternConverter.class);
127     rules.put("ndc", NDCPatternConverter.class);
128
129     rules.put("X", PropertiesPatternConverter.class);
130     rules.put("properties", PropertiesPatternConverter.class);
131
132     rules.put("sn", SequenceNumberPatternConverter.class);
133     rules.put("sequenceNumber", SequenceNumberPatternConverter.class);
134
135     rules.put("throwable", ThrowableInformationPatternConverter.class);
136     PATTERN_LAYOUT_RULES = new ReadOnlyMap(rules);
137
138     Map fnameRules = new HashMap(4);
139     fnameRules.put("d", FileDatePatternConverter.class);
140     fnameRules.put("date", FileDatePatternConverter.class);
141     fnameRules.put("i", IntegerPatternConverter.class);
142     fnameRules.put("index", IntegerPatternConverter.class);
143
144     FILENAME_PATTERN_RULES = new ReadOnlyMap(fnameRules);
145   }
146
147   /**
148    * Private constructor.
149    */
150   private PatternParser() {
151   }
152
153   /**
154    * Get standard format specifiers for EnhancedPatternLayout.
155    * @return read-only map of format converter classes keyed by format specifier strings.
156    */
157   public static Map getPatternLayoutRules() {
158     return PATTERN_LAYOUT_RULES;
159   }
160
161   /**
162    * Get standard format specifiers for rolling file appender file specification.
163    * @return read-only map of format converter classes keyed by format specifier strings.
164    */
165   public static Map getFileNamePatternRules() {
166     return FILENAME_PATTERN_RULES;
167   }
168
169   /** Extract the converter identifier found at position i.
170    *
171    * After this function returns, the variable i will point to the
172    * first char after the end of the converter identifier.
173    *
174    * If i points to a char which is not a character acceptable at the
175    * start of a unicode identifier, the value null is returned.
176    *
177    * @param lastChar last processed character.
178    * @param pattern format string.
179    * @param i current index into pattern format.
180    * @param convBuf buffer to receive conversion specifier.
181    * @param currentLiteral literal to be output in case format specifier in unrecognized.
182    * @return position in pattern after converter.
183    */
184   private static int extractConverter(
185     char lastChar, final String pattern, int i, final StringBuffer convBuf,
186     final StringBuffer currentLiteral) {
187     convBuf.setLength(0);
188
189     // When this method is called, lastChar points to the first character of the
190     // conversion word. For example:
191     // For "%hello"     lastChar = 'h'
192     // For "%-5hello"   lastChar = 'h'
193     //System.out.println("lastchar is "+lastChar);
194     if (!Character.isUnicodeIdentifierStart(lastChar)) {
195       return i;
196     }
197
198     convBuf.append(lastChar);
199
200     while (
201       (i < pattern.length())
202         && Character.isUnicodeIdentifierPart(pattern.charAt(i))) {
203       convBuf.append(pattern.charAt(i));
204       currentLiteral.append(pattern.charAt(i));
205
206       //System.out.println("conv buffer is now ["+convBuf+"].");
207       i++;
208     }
209
210     return i;
211   }
212
213   /**
214    * Extract options.
215    * @param pattern conversion pattern.
216    * @param i start of options.
217    * @param options array to receive extracted options
218    * @return position in pattern after options.
219    */
220   private static int extractOptions(String pattern, int i, List options) {
221     while ((i < pattern.length()) && (pattern.charAt(i) == '{')) {
222       int end = pattern.indexOf('}', i);
223
224       if (end == -1) {
225         break;
226       }
227
228       String r = pattern.substring(i + 1, end);
229       options.add(r);
230       i = end + 1;
231     }
232
233     return i;
234   }
235
236   /**
237    * Parse a format specifier.
238    * @param pattern pattern to parse.
239    * @param patternConverters list to receive pattern converters.
240    * @param formattingInfos list to receive field specifiers corresponding to pattern converters.
241    * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
242    * @param rules map of stock pattern converters keyed by format specifier.
243    */
244   public static void parse(
245     final String pattern, final List patternConverters,
246     final List formattingInfos, final Map converterRegistry, final Map rules) {
247     if (pattern == null) {
248       throw new NullPointerException("pattern");
249     }
250
251     StringBuffer currentLiteral = new StringBuffer(32);
252
253     int patternLength = pattern.length();
254     int state = LITERAL_STATE;
255     char c;
256     int i = 0;
257     FormattingInfo formattingInfo = FormattingInfo.getDefault();
258
259     while (i < patternLength) {
260       c = pattern.charAt(i++);
261
262       switch (state) {
263       case LITERAL_STATE:
264
265         // In literal state, the last char is always a literal.
266         if (i == patternLength) {
267           currentLiteral.append(c);
268
269           continue;
270         }
271
272         if (c == ESCAPE_CHAR) {
273           // peek at the next char.
274           switch (pattern.charAt(i)) {
275           case ESCAPE_CHAR:
276             currentLiteral.append(c);
277             i++; // move pointer
278
279             break;
280
281           default:
282
283             if (currentLiteral.length() != 0) {
284               patternConverters.add(
285                 new LiteralPatternConverter(currentLiteral.toString()));
286               formattingInfos.add(FormattingInfo.getDefault());
287             }
288
289             currentLiteral.setLength(0);
290             currentLiteral.append(c); // append %
291             state = CONVERTER_STATE;
292             formattingInfo = FormattingInfo.getDefault();
293           }
294         } else {
295           currentLiteral.append(c);
296         }
297
298         break;
299
300       case CONVERTER_STATE:
301         currentLiteral.append(c);
302
303         switch (c) {
304         case '-':
305           formattingInfo =
306             new FormattingInfo(
307               true, 
308               formattingInfo.isRightTruncated(),
309               formattingInfo.getMinLength(),
310               formattingInfo.getMaxLength());
311           break;
312
313         case '!':
314           formattingInfo =
315             new FormattingInfo(
316               formattingInfo.isLeftAligned(), 
317               true,
318               formattingInfo.getMinLength(),
319               formattingInfo.getMaxLength());
320           break;
321
322
323         case '.':
324           state = DOT_STATE;
325
326           break;
327
328         default:
329
330           if ((c >= '0') && (c <= '9')) {
331             formattingInfo =
332               new FormattingInfo(
333                 formattingInfo.isLeftAligned(), 
334                 formattingInfo.isRightTruncated(),
335                 c - '0',
336                 formattingInfo.getMaxLength());
337             state = MIN_STATE;
338           } else {
339             i = finalizeConverter(
340                 c, pattern, i, currentLiteral, formattingInfo,
341                 converterRegistry, rules, patternConverters, formattingInfos);
342
343             // Next pattern is assumed to be a literal.
344             state = LITERAL_STATE;
345             formattingInfo = FormattingInfo.getDefault();
346             currentLiteral.setLength(0);
347           }
348         } // switch
349
350         break;
351
352       case MIN_STATE:
353         currentLiteral.append(c);
354
355         if ((c >= '0') && (c <= '9')) {
356           formattingInfo =
357             new FormattingInfo(
358               formattingInfo.isLeftAligned(),
359               formattingInfo.isRightTruncated(),
360               (formattingInfo.getMinLength() * 10) + (c - '0'),
361               formattingInfo.getMaxLength());
362         } else if (c == '.') {
363           state = DOT_STATE;
364         } else {
365           i = finalizeConverter(
366               c, pattern, i, currentLiteral, formattingInfo,
367               converterRegistry, rules, patternConverters, formattingInfos);
368           state = LITERAL_STATE;
369           formattingInfo = FormattingInfo.getDefault();
370           currentLiteral.setLength(0);
371         }
372
373         break;
374
375       case DOT_STATE:
376         currentLiteral.append(c);
377
378         if ((c >= '0') && (c <= '9')) {
379           formattingInfo =
380             new FormattingInfo(
381               formattingInfo.isLeftAligned(), 
382               formattingInfo.isRightTruncated(),
383               formattingInfo.getMinLength(),
384               c - '0');
385           state = MAX_STATE;
386         } else {
387             LogLog.error(
388               "Error occured in position " + i
389               + ".\n Was expecting digit, instead got char \"" + c + "\".");
390
391           state = LITERAL_STATE;
392         }
393
394         break;
395
396       case MAX_STATE:
397         currentLiteral.append(c);
398
399         if ((c >= '0') && (c <= '9')) {
400           formattingInfo =
401             new FormattingInfo(
402               formattingInfo.isLeftAligned(), 
403               formattingInfo.isRightTruncated(),
404               formattingInfo.getMinLength(),
405               (formattingInfo.getMaxLength() * 10) + (c - '0'));
406         } else {
407           i = finalizeConverter(
408               c, pattern, i, currentLiteral, formattingInfo,
409               converterRegistry, rules, patternConverters, formattingInfos);
410           state = LITERAL_STATE;
411           formattingInfo = FormattingInfo.getDefault();
412           currentLiteral.setLength(0);
413         }
414
415         break;
416       } // switch
417     }
418
419     // while
420     if (currentLiteral.length() != 0) {
421       patternConverters.add(
422         new LiteralPatternConverter(currentLiteral.toString()));
423       formattingInfos.add(FormattingInfo.getDefault());
424     }
425   }
426
427   /**
428    * Creates a new PatternConverter.
429    *
430    *
431    * @param converterId converterId.
432    * @param currentLiteral literal to be used if converter is unrecognized or following converter
433    *    if converterId contains extra characters.
434    * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null.
435    * @param rules map of stock pattern converters keyed by format specifier.
436    * @param options converter options.
437    * @return  converter or null.
438    */
439   private static PatternConverter createConverter(
440     final String converterId, final StringBuffer currentLiteral,
441     final Map converterRegistry, final Map rules, final List options) {
442     String converterName = converterId;
443     Object converterObj = null;
444
445     for (int i = converterId.length(); (i > 0) && (converterObj == null);
446         i--) {
447       converterName = converterName.substring(0, i);
448
449       if (converterRegistry != null) {
450         converterObj = converterRegistry.get(converterName);
451       }
452
453       if ((converterObj == null) && (rules != null)) {
454         converterObj = rules.get(converterName);
455       }
456     }
457
458     if (converterObj == null) {
459         LogLog.error("Unrecognized format specifier [" + converterId + "]");
460
461       return null;
462     }
463
464     Class converterClass = null;
465
466     if (converterObj instanceof Class) {
467       converterClass = (Class) converterObj;
468     } else {
469       if (converterObj instanceof String) {
470         try {
471           converterClass = Loader.loadClass((String) converterObj);
472         } catch (ClassNotFoundException ex) {
473             LogLog.warn(
474               "Class for conversion pattern %" + converterName + " not found",
475               ex);
476
477           return null;
478         }
479       } else {
480           LogLog.warn(
481             "Bad map entry for conversion pattern %" +  converterName + ".");
482
483         return null;
484       }
485     }
486
487     try {
488       Method factory =
489         converterClass.getMethod(
490           "newInstance",
491           new Class[] {
492             Class.forName("[Ljava.lang.String;")
493           });
494       String[] optionsArray = new String[options.size()];
495       optionsArray = (String[]) options.toArray(optionsArray);
496
497       Object newObj =
498         factory.invoke(null, new Object[] { optionsArray });
499
500       if (newObj instanceof PatternConverter) {
501         currentLiteral.delete(
502           0,
503           currentLiteral.length()
504           - (converterId.length() - converterName.length()));
505
506         return (PatternConverter) newObj;
507       } else {
508           LogLog.warn(
509             "Class " + converterClass.getName()
510             + " does not extend PatternConverter.");
511       }
512     } catch (Exception ex) {
513         LogLog.error("Error creating converter for " + converterId, ex);
514
515       try {
516         //
517         //  try default constructor
518         PatternConverter pc = (PatternConverter) converterClass.newInstance();
519         currentLiteral.delete(
520           0,
521           currentLiteral.length()
522           - (converterId.length() - converterName.length()));
523
524         return pc;
525       } catch (Exception ex2) {
526           LogLog.error("Error creating converter for " + converterId, ex2);
527       }
528     }
529
530     return null;
531   }
532
533   /**
534    * Processes a format specifier sequence.
535    *
536    * @param c initial character of format specifier.
537    * @param pattern conversion pattern
538    * @param i current position in conversion pattern.
539    * @param currentLiteral current literal.
540    * @param formattingInfo current field specifier.
541    * @param converterRegistry map of user-provided pattern converters keyed by format specifier, may be null.
542    * @param rules map of stock pattern converters keyed by format specifier.
543    * @param patternConverters list to receive parsed pattern converter.
544    * @param formattingInfos list to receive corresponding field specifier.
545    * @return position after format specifier sequence.
546    */
547   private static int finalizeConverter(
548     char c, String pattern, int i,
549     final StringBuffer currentLiteral, final FormattingInfo formattingInfo,
550     final Map converterRegistry, final Map rules, final List patternConverters,
551     final List formattingInfos) {
552     StringBuffer convBuf = new StringBuffer();
553     i = extractConverter(c, pattern, i, convBuf, currentLiteral);
554
555     String converterId = convBuf.toString();
556
557     List options = new ArrayList();
558     i = extractOptions(pattern, i, options);
559
560     PatternConverter pc =
561       createConverter(
562         converterId, currentLiteral, converterRegistry, rules, options);
563
564     if (pc == null) {
565       StringBuffer msg;
566
567       if ((converterId == null) || (converterId.length() == 0)) {
568         msg =
569           new StringBuffer("Empty conversion specifier starting at position ");
570       } else {
571         msg = new StringBuffer("Unrecognized conversion specifier [");
572         msg.append(converterId);
573         msg.append("] starting at position ");
574       }
575
576       msg.append(Integer.toString(i));
577       msg.append(" in conversion pattern.");
578
579         LogLog.error(msg.toString());
580
581       patternConverters.add(
582         new LiteralPatternConverter(currentLiteral.toString()));
583       formattingInfos.add(FormattingInfo.getDefault());
584     } else {
585       patternConverters.add(pc);
586       formattingInfos.add(formattingInfo);
587
588       if (currentLiteral.length() > 0) {
589         patternConverters.add(
590           new LiteralPatternConverter(currentLiteral.toString()));
591         formattingInfos.add(FormattingInfo.getDefault());
592       }
593     }
594
595     currentLiteral.setLength(0);
596
597     return i;
598   }
599
600   /**
601    * The class wraps another Map but throws exceptions on any attempt to modify the map.
602    */
603   private static class ReadOnlyMap implements Map {
604     /**
605      * Wrapped map.
606      */
607     private final Map map;
608
609     /**
610      * Constructor
611      * @param src source map.
612      */
613     public ReadOnlyMap(Map src) {
614       map = src;
615     }
616
617     /**
618      * {@inheritDoc}
619      */
620     public void clear() {
621       throw new UnsupportedOperationException();
622     }
623
624     /**
625      * {@inheritDoc}
626      */
627     public boolean containsKey(Object key) {
628       return map.containsKey(key);
629     }
630
631     /**
632      * {@inheritDoc}
633      */
634     public boolean containsValue(Object value) {
635       return map.containsValue(value);
636     }
637
638     /**
639      * {@inheritDoc}
640      */
641     public Set entrySet() {
642       return map.entrySet();
643     }
644
645     /**
646      * {@inheritDoc}
647      */
648     public Object get(Object key) {
649       return map.get(key);
650     }
651
652     /**
653      * {@inheritDoc}
654      */
655     public boolean isEmpty() {
656       return map.isEmpty();
657     }
658
659     /**
660      * {@inheritDoc}
661      */
662     public Set keySet() {
663       return map.keySet();
664     }
665
666     /**
667      * {@inheritDoc}
668      */
669     public Object put(Object key, Object value) {
670       throw new UnsupportedOperationException();
671     }
672
673     /**
674      * {@inheritDoc}
675      */
676     public void putAll(Map t) {
677       throw new UnsupportedOperationException();
678     }
679
680     /**
681      * {@inheritDoc}
682      */
683     public Object remove(Object key) {
684       throw new UnsupportedOperationException();
685     }
686
687     /**
688      * {@inheritDoc}
689      */
690     public int size() {
691       return map.size();
692     }
693
694     /**
695      * {@inheritDoc}
696      */
697     public Collection values() {
698       return map.values();
699     }
700   }
701 }