| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.log4j.pattern; |
| |
| import org.apache.log4j.helpers.Loader; |
| import org.apache.log4j.helpers.LogLog; |
| |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| // Contributors: Nelson Minar <(nelson@monkey.org> |
| // Igor E. Poteryaev <jah@mail.ru> |
| // Reinhard Deschler <reinhard.deschler@web.de> |
| |
| /** |
| * Most of the work of the {@link org.apache.log4j.EnhancedPatternLayout} class |
| * is delegated to the ExtrasPatternParser class. |
| * <p>It is this class that parses conversion patterns and creates |
| * a chained list of {@link PatternConverter PatternConverters}. |
| * |
| * @author James P. Cakalic |
| * @author Ceki Gülcü |
| * @author Anders Kristensen |
| * @author Paul Smith |
| * @author Curt Arnold |
| * |
| */ |
| public final class ExtrasPatternParser { |
| /** |
| * Escape character for format specifier. |
| */ |
| private static final char ESCAPE_CHAR = '%'; |
| |
| /** |
| * Literal state. |
| */ |
| private static final int LITERAL_STATE = 0; |
| |
| /** |
| * In converter name state. |
| */ |
| private static final int CONVERTER_STATE = 1; |
| |
| /** |
| * Dot state. |
| */ |
| private static final int DOT_STATE = 3; |
| |
| /** |
| * Min state. |
| */ |
| private static final int MIN_STATE = 4; |
| |
| /** |
| * Max state. |
| */ |
| private static final int MAX_STATE = 5; |
| |
| /** |
| * Standard format specifiers for EnhancedPatternLayout. |
| */ |
| private static final Map PATTERN_LAYOUT_RULES; |
| |
| /** |
| * Standard format specifiers for rolling file appenders. |
| */ |
| private static final Map FILENAME_PATTERN_RULES; |
| |
| static { |
| // We set the global rules in the static initializer of ExtrasPatternParser class |
| Map rules = new HashMap(17); |
| rules.put("c", LoggerPatternConverter.class); |
| rules.put("logger", LoggerPatternConverter.class); |
| |
| rules.put("C", ClassNamePatternConverter.class); |
| rules.put("class", ClassNamePatternConverter.class); |
| |
| rules.put("d", DatePatternConverter.class); |
| rules.put("date", DatePatternConverter.class); |
| |
| rules.put("F", FileLocationPatternConverter.class); |
| rules.put("file", FileLocationPatternConverter.class); |
| |
| rules.put("l", FullLocationPatternConverter.class); |
| |
| rules.put("L", LineLocationPatternConverter.class); |
| rules.put("line", LineLocationPatternConverter.class); |
| |
| rules.put("m", MessagePatternConverter.class); |
| rules.put("message", MessagePatternConverter.class); |
| |
| rules.put("n", LineSeparatorPatternConverter.class); |
| |
| rules.put("M", MethodLocationPatternConverter.class); |
| rules.put("method", MethodLocationPatternConverter.class); |
| |
| rules.put("p", LevelPatternConverter.class); |
| rules.put("level", LevelPatternConverter.class); |
| |
| rules.put("r", RelativeTimePatternConverter.class); |
| rules.put("relative", RelativeTimePatternConverter.class); |
| |
| rules.put("t", ThreadPatternConverter.class); |
| rules.put("thread", ThreadPatternConverter.class); |
| |
| rules.put("x", NDCPatternConverter.class); |
| rules.put("ndc", NDCPatternConverter.class); |
| |
| rules.put("X", PropertiesPatternConverter.class); |
| rules.put("properties", PropertiesPatternConverter.class); |
| |
| rules.put("sn", SequenceNumberPatternConverter.class); |
| rules.put("sequenceNumber", SequenceNumberPatternConverter.class); |
| |
| rules.put("throwable", ThrowableInformationPatternConverter.class); |
| PATTERN_LAYOUT_RULES = new ReadOnlyMap(rules); |
| |
| Map fnameRules = new HashMap(4); |
| fnameRules.put("d", FileDatePatternConverter.class); |
| fnameRules.put("date", FileDatePatternConverter.class); |
| fnameRules.put("i", IntegerPatternConverter.class); |
| fnameRules.put("index", IntegerPatternConverter.class); |
| |
| FILENAME_PATTERN_RULES = new ReadOnlyMap(fnameRules); |
| } |
| |
| /** |
| * Private constructor. |
| */ |
| private ExtrasPatternParser() { |
| } |
| |
| /** |
| * Get standard format specifiers for EnhancedPatternLayout. |
| * @return read-only map of format converter classes keyed by format specifier strings. |
| */ |
| public static Map getPatternLayoutRules() { |
| return PATTERN_LAYOUT_RULES; |
| } |
| |
| /** |
| * Get standard format specifiers for rolling file appender file specification. |
| * @return read-only map of format converter classes keyed by format specifier strings. |
| */ |
| public static Map getFileNamePatternRules() { |
| return FILENAME_PATTERN_RULES; |
| } |
| |
| /** Extract the converter identifier found at position i. |
| * |
| * After this function returns, the variable i will point to the |
| * first char after the end of the converter identifier. |
| * |
| * If i points to a char which is not a character acceptable at the |
| * start of a unicode identifier, the value null is returned. |
| * |
| * @param lastChar last processed character. |
| * @param pattern format string. |
| * @param i current index into pattern format. |
| * @param convBuf buffer to receive conversion specifier. |
| * @param currentLiteral literal to be output in case format specifier in unrecognized. |
| * @return position in pattern after converter. |
| */ |
| private static int extractConverter( |
| char lastChar, final String pattern, int i, final StringBuffer convBuf, |
| final StringBuffer currentLiteral) { |
| convBuf.setLength(0); |
| |
| // When this method is called, lastChar points to the first character of the |
| // conversion word. For example: |
| // For "%hello" lastChar = 'h' |
| // For "%-5hello" lastChar = 'h' |
| //System.out.println("lastchar is "+lastChar); |
| if (!Character.isUnicodeIdentifierStart(lastChar)) { |
| return i; |
| } |
| |
| convBuf.append(lastChar); |
| |
| while ( |
| (i < pattern.length()) |
| && Character.isUnicodeIdentifierPart(pattern.charAt(i))) { |
| convBuf.append(pattern.charAt(i)); |
| currentLiteral.append(pattern.charAt(i)); |
| |
| //System.out.println("conv buffer is now ["+convBuf+"]."); |
| i++; |
| } |
| |
| return i; |
| } |
| |
| /** |
| * Extract options. |
| * @param pattern conversion pattern. |
| * @param i start of options. |
| * @param options array to receive extracted options |
| * @return position in pattern after options. |
| */ |
| private static int extractOptions(String pattern, int i, List options) { |
| while ((i < pattern.length()) && (pattern.charAt(i) == '{')) { |
| int end = pattern.indexOf('}', i); |
| |
| if (end == -1) { |
| break; |
| } |
| |
| String r = pattern.substring(i + 1, end); |
| options.add(r); |
| i = end + 1; |
| } |
| |
| return i; |
| } |
| |
| /** |
| * Parse a format specifier. |
| * @param pattern pattern to parse. |
| * @param patternConverters list to receive pattern converters. |
| * @param formattingInfos list to receive field specifiers corresponding to pattern converters. |
| * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null. |
| * @param rules map of stock pattern converters keyed by format specifier. |
| */ |
| public static void parse( |
| final String pattern, final List patternConverters, |
| final List formattingInfos, final Map converterRegistry, final Map rules) { |
| if (pattern == null) { |
| throw new NullPointerException("pattern"); |
| } |
| |
| StringBuffer currentLiteral = new StringBuffer(32); |
| |
| int patternLength = pattern.length(); |
| int state = LITERAL_STATE; |
| char c; |
| int i = 0; |
| ExtrasFormattingInfo formattingInfo = ExtrasFormattingInfo.getDefault(); |
| |
| while (i < patternLength) { |
| c = pattern.charAt(i++); |
| |
| switch (state) { |
| case LITERAL_STATE: |
| |
| // In literal state, the last char is always a literal. |
| if (i == patternLength) { |
| currentLiteral.append(c); |
| |
| continue; |
| } |
| |
| if (c == ESCAPE_CHAR) { |
| // peek at the next char. |
| switch (pattern.charAt(i)) { |
| case ESCAPE_CHAR: |
| currentLiteral.append(c); |
| i++; // move pointer |
| |
| break; |
| |
| default: |
| |
| if (currentLiteral.length() != 0) { |
| patternConverters.add( |
| new LiteralPatternConverter(currentLiteral.toString())); |
| formattingInfos.add(ExtrasFormattingInfo.getDefault()); |
| } |
| |
| currentLiteral.setLength(0); |
| currentLiteral.append(c); // append % |
| state = CONVERTER_STATE; |
| formattingInfo = ExtrasFormattingInfo.getDefault(); |
| } |
| } else { |
| currentLiteral.append(c); |
| } |
| |
| break; |
| |
| case CONVERTER_STATE: |
| currentLiteral.append(c); |
| |
| switch (c) { |
| case '-': |
| formattingInfo = |
| new ExtrasFormattingInfo( |
| true, |
| formattingInfo.isRightTruncated(), |
| formattingInfo.getMinLength(), |
| formattingInfo.getMaxLength()); |
| break; |
| |
| case '!': |
| formattingInfo = |
| new ExtrasFormattingInfo( |
| formattingInfo.isLeftAligned(), |
| true, |
| formattingInfo.getMinLength(), |
| formattingInfo.getMaxLength()); |
| break; |
| |
| |
| case '.': |
| state = DOT_STATE; |
| |
| break; |
| |
| default: |
| |
| if ((c >= '0') && (c <= '9')) { |
| formattingInfo = |
| new ExtrasFormattingInfo( |
| formattingInfo.isLeftAligned(), |
| formattingInfo.isRightTruncated(), |
| c - '0', |
| formattingInfo.getMaxLength()); |
| state = MIN_STATE; |
| } else { |
| i = finalizeConverter( |
| c, pattern, i, currentLiteral, formattingInfo, |
| converterRegistry, rules, patternConverters, formattingInfos); |
| |
| // Next pattern is assumed to be a literal. |
| state = LITERAL_STATE; |
| formattingInfo = ExtrasFormattingInfo.getDefault(); |
| currentLiteral.setLength(0); |
| } |
| } // switch |
| |
| break; |
| |
| case MIN_STATE: |
| currentLiteral.append(c); |
| |
| if ((c >= '0') && (c <= '9')) { |
| formattingInfo = |
| new ExtrasFormattingInfo( |
| formattingInfo.isLeftAligned(), |
| formattingInfo.isRightTruncated(), |
| (formattingInfo.getMinLength() * 10) + (c - '0'), |
| formattingInfo.getMaxLength()); |
| } else if (c == '.') { |
| state = DOT_STATE; |
| } else { |
| i = finalizeConverter( |
| c, pattern, i, currentLiteral, formattingInfo, |
| converterRegistry, rules, patternConverters, formattingInfos); |
| state = LITERAL_STATE; |
| formattingInfo = ExtrasFormattingInfo.getDefault(); |
| currentLiteral.setLength(0); |
| } |
| |
| break; |
| |
| case DOT_STATE: |
| currentLiteral.append(c); |
| |
| if ((c >= '0') && (c <= '9')) { |
| formattingInfo = |
| new ExtrasFormattingInfo( |
| formattingInfo.isLeftAligned(), |
| formattingInfo.isRightTruncated(), |
| formattingInfo.getMinLength(), |
| c - '0'); |
| state = MAX_STATE; |
| } else { |
| LogLog.error( |
| "Error occured in position " + i |
| + ".\n Was expecting digit, instead got char \"" + c + "\"."); |
| |
| state = LITERAL_STATE; |
| } |
| |
| break; |
| |
| case MAX_STATE: |
| currentLiteral.append(c); |
| |
| if ((c >= '0') && (c <= '9')) { |
| formattingInfo = |
| new ExtrasFormattingInfo( |
| formattingInfo.isLeftAligned(), |
| formattingInfo.isRightTruncated(), |
| formattingInfo.getMinLength(), |
| (formattingInfo.getMaxLength() * 10) + (c - '0')); |
| } else { |
| i = finalizeConverter( |
| c, pattern, i, currentLiteral, formattingInfo, |
| converterRegistry, rules, patternConverters, formattingInfos); |
| state = LITERAL_STATE; |
| formattingInfo = ExtrasFormattingInfo.getDefault(); |
| currentLiteral.setLength(0); |
| } |
| |
| break; |
| } // switch |
| } |
| |
| // while |
| if (currentLiteral.length() != 0) { |
| patternConverters.add( |
| new LiteralPatternConverter(currentLiteral.toString())); |
| formattingInfos.add(ExtrasFormattingInfo.getDefault()); |
| } |
| } |
| |
| /** |
| * Creates a new PatternConverter. |
| * |
| * |
| * @param converterId converterId. |
| * @param currentLiteral literal to be used if converter is unrecognized or following converter |
| * if converterId contains extra characters. |
| * @param converterRegistry map of user-supported pattern converters keyed by format specifier, may be null. |
| * @param rules map of stock pattern converters keyed by format specifier. |
| * @param options converter options. |
| * @return converter or null. |
| */ |
| private static PatternConverter createConverter( |
| final String converterId, final StringBuffer currentLiteral, |
| final Map converterRegistry, final Map rules, final List options) { |
| String converterName = converterId; |
| Object converterObj = null; |
| |
| for (int i = converterId.length(); (i > 0) && (converterObj == null); |
| i--) { |
| converterName = converterName.substring(0, i); |
| |
| if (converterRegistry != null) { |
| converterObj = converterRegistry.get(converterName); |
| } |
| |
| if ((converterObj == null) && (rules != null)) { |
| converterObj = rules.get(converterName); |
| } |
| } |
| |
| if (converterObj == null) { |
| LogLog.error("Unrecognized format specifier [" + converterId + "]"); |
| |
| return null; |
| } |
| |
| Class converterClass; |
| |
| if (converterObj instanceof Class) { |
| converterClass = (Class) converterObj; |
| } else { |
| if (converterObj instanceof String) { |
| try { |
| converterClass = Loader.loadClass((String) converterObj); |
| } catch (ClassNotFoundException ex) { |
| LogLog.warn( |
| "Class for conversion pattern %" + converterName + " not found", |
| ex); |
| |
| return null; |
| } |
| } else { |
| LogLog.warn( |
| "Bad map entry for conversion pattern %" + converterName + "."); |
| |
| return null; |
| } |
| } |
| |
| try { |
| Method factory = |
| converterClass.getMethod( |
| "newInstance", |
| new Class[] { |
| Class.forName("[Ljava.lang.String;") |
| }); |
| String[] optionsArray = new String[options.size()]; |
| optionsArray = (String[]) options.toArray(optionsArray); |
| |
| Object newObj = |
| factory.invoke(null, new Object[] { optionsArray }); |
| |
| if (newObj instanceof PatternConverter) { |
| currentLiteral.delete( |
| 0, |
| currentLiteral.length() |
| - (converterId.length() - converterName.length())); |
| |
| return (PatternConverter) newObj; |
| } else { |
| LogLog.warn( |
| "Class " + converterClass.getName() |
| + " does not extend PatternConverter."); |
| } |
| } catch (Exception ex) { |
| LogLog.error("Error creating converter for " + converterId, ex); |
| |
| try { |
| // |
| // try default constructor |
| PatternConverter pc = (PatternConverter) converterClass.newInstance(); |
| currentLiteral.delete( |
| 0, |
| currentLiteral.length() |
| - (converterId.length() - converterName.length())); |
| |
| return pc; |
| } catch (Exception ex2) { |
| LogLog.error("Error creating converter for " + converterId, ex2); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Processes a format specifier sequence. |
| * |
| * @param c initial character of format specifier. |
| * @param pattern conversion pattern |
| * @param i current position in conversion pattern. |
| * @param currentLiteral current literal. |
| * @param formattingInfo current field specifier. |
| * @param converterRegistry map of user-provided pattern converters keyed by format specifier, may be null. |
| * @param rules map of stock pattern converters keyed by format specifier. |
| * @param patternConverters list to receive parsed pattern converter. |
| * @param formattingInfos list to receive corresponding field specifier. |
| * @return position after format specifier sequence. |
| */ |
| private static int finalizeConverter( |
| char c, String pattern, int i, |
| final StringBuffer currentLiteral, final ExtrasFormattingInfo formattingInfo, |
| final Map converterRegistry, final Map rules, final List patternConverters, |
| final List formattingInfos) { |
| StringBuffer convBuf = new StringBuffer(); |
| i = extractConverter(c, pattern, i, convBuf, currentLiteral); |
| |
| String converterId = convBuf.toString(); |
| |
| List options = new ArrayList(); |
| i = extractOptions(pattern, i, options); |
| |
| PatternConverter pc = |
| createConverter( |
| converterId, currentLiteral, converterRegistry, rules, options); |
| |
| if (pc == null) { |
| StringBuffer msg; |
| |
| if (converterId.length() == 0) { |
| msg = |
| new StringBuffer("Empty conversion specifier starting at position "); |
| } else { |
| msg = new StringBuffer("Unrecognized conversion specifier ["); |
| msg.append(converterId); |
| msg.append("] starting at position "); |
| } |
| |
| msg.append(Integer.toString(i)); |
| msg.append(" in conversion pattern."); |
| |
| LogLog.error(msg.toString()); |
| |
| patternConverters.add( |
| new LiteralPatternConverter(currentLiteral.toString())); |
| formattingInfos.add(ExtrasFormattingInfo.getDefault()); |
| } else { |
| patternConverters.add(pc); |
| formattingInfos.add(formattingInfo); |
| |
| if (currentLiteral.length() > 0) { |
| patternConverters.add( |
| new LiteralPatternConverter(currentLiteral.toString())); |
| formattingInfos.add(ExtrasFormattingInfo.getDefault()); |
| } |
| } |
| |
| currentLiteral.setLength(0); |
| |
| return i; |
| } |
| |
| /** |
| * The class wraps another Map but throws exceptions on any attempt to modify the map. |
| */ |
| private static class ReadOnlyMap implements Map { |
| /** |
| * Wrapped map. |
| */ |
| private final Map map; |
| |
| /** |
| * Constructor |
| * @param src source map. |
| */ |
| public ReadOnlyMap(Map src) { |
| map = src; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void clear() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean containsKey(Object key) { |
| return map.containsKey(key); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean containsValue(Object value) { |
| return map.containsValue(value); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Set entrySet() { |
| return map.entrySet(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Object get(Object key) { |
| return map.get(key); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean isEmpty() { |
| return map.isEmpty(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Set keySet() { |
| return map.keySet(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Object put(Object key, Object value) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void putAll(Map t) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Object remove(Object key) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public int size() { |
| return map.size(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Collection values() { |
| return map.values(); |
| } |
| } |
| } |