| /* |
| * ============================================================================ |
| * The Apache Software License, Version 1.1 |
| * ============================================================================ |
| * |
| * Copyright (C) 1999 The Apache Software Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without modifica- |
| * tion, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, if any, must |
| * include the following acknowledgment: "This product includes software |
| * developed by the Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, if |
| * and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "log4j" and "Apache Software Foundation" must not be used to |
| * endorse or promote products derived from this software without prior |
| * written permission. For written permission, please contact |
| * apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", nor may |
| * "Apache" appear in their name, without prior written permission of the |
| * Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
| * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
| * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- |
| * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
| * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * This software consists of voluntary contributions made by many individuals |
| * on behalf of the Apache Software Foundation. For more information on the |
| * Apache Software Foundation, please see <http://www.apache.org/>. |
| * |
| */ |
| |
| package org.apache.log4j.pattern; |
| |
| import org.apache.log4j.Layout; |
| import org.apache.log4j.helpers.AbsoluteTimeDateFormat; |
| import org.apache.log4j.helpers.LogLog; |
| import org.apache.log4j.helpers.OptionConverter; |
| |
| import java.util.Hashtable; |
| |
| |
| // 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.PatternLayout} class |
| is delegated to the PatternParser class. |
| |
| <p>It is this class that parses conversion patterns and creates |
| a chained list of {@link OptionConverter OptionConverters}. |
| |
| @author <a href=mailto:"cakalijp@Maritz.com">James P. Cakalic</a> |
| @author Ceki Gülcü |
| @author Anders Kristensen |
| |
| @since 0.8.2 |
| */ |
| public class PatternParser { |
| private static final char ESCAPE_CHAR = '%'; |
| private static final int LITERAL_STATE = 0; |
| private static final int CONVERTER_STATE = 1; |
| private static final int MINUS_STATE = 2; |
| private static final int DOT_STATE = 3; |
| private static final int MIN_STATE = 4; |
| private static final int MAX_STATE = 5; |
| static final int FULL_LOCATION_CONVERTER = 1000; |
| static final int METHOD_LOCATION_CONVERTER = 1001; |
| static final int CLASS_LOCATION_CONVERTER = 1002; |
| static final int LINE_LOCATION_CONVERTER = 1003; |
| static final int FILE_LOCATION_CONVERTER = 1004; |
| static final int RELATIVE_TIME_CONVERTER = 2000; |
| static final int THREAD_CONVERTER = 2001; |
| static final int LEVEL_CONVERTER = 2002; |
| static final int NDC_CONVERTER = 2003; |
| static final int MESSAGE_CONVERTER = 2004; |
| int state; |
| protected StringBuffer currentLiteral = new StringBuffer(32); |
| protected int patternLength; |
| protected int i; |
| PatternConverter head; |
| PatternConverter tail; |
| protected FormattingInfo formattingInfo = new FormattingInfo(); |
| protected String pattern; |
| Hashtable converterRegistry; |
| |
| public PatternParser(String pattern) { |
| this.pattern = pattern; |
| patternLength = pattern.length(); |
| state = LITERAL_STATE; |
| } |
| |
| private void addToList(PatternConverter pc) { |
| if (head == null) { |
| head = tail = pc; |
| } else { |
| tail.next = pc; |
| tail = pc; |
| } |
| } |
| |
| /** 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. |
| * |
| */ |
| protected String extractConverter() { |
| StringBuffer convBuf = new StringBuffer(16); |
| |
| if(i >= patternLength) { |
| // the pattern converter was the last character... |
| return null; |
| } |
| |
| char c = pattern.charAt(i); |
| |
| if (Character.isUnicodeIdentifierStart(c)) { |
| convBuf.append(c); |
| } else { |
| return null; |
| } |
| |
| while ( |
| (++i < patternLength) |
| && Character.isUnicodeIdentifierPart(pattern.charAt(i))) { |
| convBuf.append(pattern.charAt(i)); |
| } |
| |
| return convBuf.toString(); |
| } |
| |
| /** |
| * Returns the option, null if not in the expected format. |
| */ |
| protected String extractOption() { |
| if ((i < patternLength) && (pattern.charAt(i) == '{')) { |
| int end = pattern.indexOf('}', i); |
| |
| if (end > i) { |
| String r = pattern.substring(i + 1, end); |
| i = end + 1; |
| |
| return r; |
| } |
| } |
| |
| return null; |
| } |
| |
| public PatternConverter parse() { |
| char c; |
| i = 0; |
| |
| 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; |
| |
| case 'n': |
| currentLiteral.append(Layout.LINE_SEP); |
| i++; // move pointer |
| |
| break; |
| |
| default: |
| |
| if (currentLiteral.length() != 0) { |
| addToList( |
| new LiteralPatternConverter(currentLiteral.toString())); |
| |
| //LogLog.debug("Parsed LITERAL converter: \"" |
| // +currentLiteral+"\"."); |
| } |
| |
| currentLiteral.setLength(0); |
| currentLiteral.append(c); // append % |
| state = CONVERTER_STATE; |
| formattingInfo.reset(); |
| } |
| } else { |
| currentLiteral.append(c); |
| } |
| |
| break; |
| |
| case CONVERTER_STATE: |
| currentLiteral.append(c); |
| |
| switch (c) { |
| case '-': |
| formattingInfo.leftAlign = true; |
| |
| break; |
| |
| case '.': |
| state = DOT_STATE; |
| |
| break; |
| |
| default: |
| |
| if ((c >= '0') && (c <= '9')) { |
| formattingInfo.min = c - '0'; |
| state = MIN_STATE; |
| } else { |
| finalizeConverter(c); |
| } |
| } // switch |
| |
| break; |
| |
| case MIN_STATE: |
| currentLiteral.append(c); |
| |
| if ((c >= '0') && (c <= '9')) { |
| formattingInfo.min = (formattingInfo.min * 10) + (c - '0'); |
| } else if (c == '.') { |
| state = DOT_STATE; |
| } else { |
| finalizeConverter(c); |
| } |
| |
| break; |
| |
| case DOT_STATE: |
| currentLiteral.append(c); |
| |
| if ((c >= '0') && (c <= '9')) { |
| formattingInfo.max = 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.max = (formattingInfo.max * 10) + (c - '0'); |
| } else { |
| finalizeConverter(c); |
| state = LITERAL_STATE; |
| } |
| |
| break; |
| } // switch |
| } |
| |
| // while |
| if (currentLiteral.length() != 0) { |
| addToList(new LiteralPatternConverter(currentLiteral.toString())); |
| |
| //LogLog.debug("Parsed LITERAL converter: \""+currentLiteral+"\"."); |
| } |
| |
| return head; |
| } |
| |
| String findConverterClass(String converterId) { |
| if (converterRegistry != null) { |
| String r = (String) converterRegistry.get(converterId); |
| |
| if (r != null) { |
| return r; |
| } |
| } |
| |
| return null; |
| } |
| |
| protected void finalizeConverter(char c) { |
| PatternConverter pc = null; |
| |
| //System.out.println("============================"); |
| |
| |
| String converterId = extractConverter(); |
| |
| //System.out.println("==============[" + converterId + "]"); |
| //System.out.println("c is [" + c + "]"); |
| |
| String className = (String) findConverterClass(converterId); |
| |
| String option = extractOption(); |
| //System.out.println("Option is [" + option + "]"); |
| |
| |
| if (className != null) { |
| pc = |
| (PatternConverter) OptionConverter.instantiateByClassName( |
| className, PatternConverter.class, null); |
| } else { |
| switch (c) { |
| case 'c': |
| pc = new LoggerPatternConverter(formattingInfo); |
| pc.setOption(option); |
| |
| //LogLog.debug("CATEGORY converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'C': |
| pc = new ClassNamePatternConverter(formattingInfo); |
| pc.setOption(option); |
| |
| //LogLog.debug("CLASS_NAME converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'd': |
| |
| String dateFormatStr = AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT; |
| |
| //DateFormat df; |
| if (option != null) { |
| dateFormatStr = option; |
| } |
| |
| if ( |
| dateFormatStr.equalsIgnoreCase( |
| AbsoluteTimeDateFormat.ISO8601_DATE_FORMAT)) { |
| option = "yyyy-mm-dd HH:mm:ss,SSS"; |
| //System.out.println("optin is " + option); |
| } else if ( |
| dateFormatStr.equalsIgnoreCase( |
| AbsoluteTimeDateFormat.ABS_TIME_DATE_FORMAT)) { |
| option = "HH:mm:ss,SSS"; |
| } else if ( |
| dateFormatStr.equalsIgnoreCase( |
| AbsoluteTimeDateFormat.DATE_AND_TIME_DATE_FORMAT)) { |
| option = "dd MMM yyyy HH:mm:ss,SSS"; |
| } |
| |
| pc = new DatePatternConverter(formattingInfo); |
| pc.setOption(option); |
| |
| //LogLog.debug("DATE converter {"+dateFormatStr+"}."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'F': |
| pc = new FileLocationPatternConverter(formattingInfo); |
| |
| //LogLog.debug("File name converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'l': |
| pc = new FullLocationPatternConverter(formattingInfo); |
| |
| //LogLog.debug("Location converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'L': |
| pc = new LineLocationPatternConverter(formattingInfo); |
| |
| //LogLog.debug("LINE NUMBER converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'm': |
| pc = new MessagePatternConverter(formattingInfo); |
| |
| //LogLog.debug("MESSAGE converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'M': |
| pc = new MethodLocationPatternConverter(formattingInfo); |
| |
| //LogLog.debug("METHOD converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'p': |
| pc = new LevelPatternConverter(formattingInfo); |
| |
| //LogLog.debug("LEVEL converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'r': |
| pc = new RelativeTimePatternConverter(formattingInfo); |
| |
| //LogLog.debug("RELATIVE time converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 't': |
| pc = new ThreadPatternConverter(formattingInfo); |
| |
| //LogLog.debug("THREAD converter."); |
| //formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| /*case 'u': |
| if(i < patternLength) { |
| char cNext = pattern.charAt(i); |
| if(cNext >= '0' && cNext <= '9') { |
| pc = new UserFieldPatternConverter(formattingInfo, cNext - '0'); |
| LogLog.debug("USER converter ["+cNext+"]."); |
| formattingInfo.dump(); |
| currentLiteral.setLength(0); |
| i++; |
| } |
| else |
| LogLog.error("Unexpected char" +cNext+" at position "+i); |
| } |
| break;*/ |
| case 'x': |
| pc = new NDCPatternConverter(formattingInfo); |
| |
| //LogLog.debug("NDC converter."); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| case 'X': |
| |
| //String xOpt = extractOption(); // extractOption returns null if no argument is supplied |
| pc = new MDCPatternConverter(formattingInfo); |
| pc.setOption(option); |
| currentLiteral.setLength(0); |
| |
| break; |
| |
| default: |
| LogLog.error( |
| "Unexpected char [" + c + "] at position " + i |
| + " in conversion patterrn."); |
| pc = new LiteralPatternConverter(currentLiteral.toString()); |
| currentLiteral.setLength(0); |
| } |
| } |
| |
| addConverter(pc); |
| } |
| |
| protected void addConverter(PatternConverter pc) { |
| currentLiteral.setLength(0); |
| |
| // Add the pattern converter to the list. |
| addToList(pc); |
| |
| // Next pattern is assumed to be a literal. |
| state = LITERAL_STATE; |
| |
| // Reset formatting info |
| formattingInfo.reset(); |
| } |
| |
| public Hashtable getConverterRegistry() { |
| return converterRegistry; |
| } |
| |
| public void setConverterRegistry(Hashtable hashtable) { |
| converterRegistry = hashtable; |
| } |
| } |