// Module:  Log4CPLUS
// File:    patternlayout.cxx
// Created: 6/2001
// Author:  Tad E. Smith
//
//
// Copyright (C) Tad E. Smith  All rights reserved.
//
// This software is published under the terms of the Apache Software
// License version 1.1, a copy of which has been included with this
// distribution in the LICENSE.APL file.
//
// $Log: patternlayout.cxx,v $
// Revision 1.18  2003/09/28 04:30:59  tcsmith
// Disable copy for BasicPatternConverter.
//
// Revision 1.17  2003/08/05 15:56:24  tcsmith
// Fixed a UNICODE compilation error.
//
// Revision 1.16  2003/08/04 01:42:51  tcsmith
// 1)  Deprecated the "Pattern" property in favor of the "ConverstionPattern" property.
// 1)  Fixed several compilation warnings.
//
// Revision 1.15  2003/07/19 15:35:12  tcsmith
// Fixed the FULL_LOCATION_CONVERTER output.
//
// Revision 1.14  2003/06/29 16:48:24  tcsmith
// Modified to support that move of the getLogLog() method into the LogLog
// class.
//
// Revision 1.13  2003/06/23 20:56:43  tcsmith
// Modified to support the changes in the spi::InternalLoggingEvent class.
//
// Revision 1.12  2003/06/13 15:27:48  tcsmith
// Modified to support changes to the InternalLoggingEvent.
//
// Revision 1.11  2003/06/11 22:55:41  tcsmith
// Updated to support the changes in the InternalLoggingEvent class.
//
// Revision 1.10  2003/06/09 18:13:17  tcsmith
// Changed the ctor to take a 'const' Properties object.
//
// Revision 1.9  2003/06/04 18:59:00  tcsmith
// Modified to use the "time" function defined in the timehelper.h header.
//
// Revision 1.8  2003/05/21 22:14:46  tcsmith
// Fixed compiler warning: "conversion from 'size_t' to 'int', possible loss
// of data".
//
// Revision 1.7  2003/05/01 19:49:32  tcsmith
// Fixed: "warning: comparison between signed and unsigned".
//
// Revision 1.6  2003/04/19 23:32:52  tcsmith
// Fixed UNICODE support.
//
// Revision 1.5  2003/04/19 23:04:31  tcsmith
// Fixed UNICODE support.
//
// Revision 1.4  2003/04/18 22:24:11  tcsmith
// Converted from std::string to log4cplus::tstring.
//
// Revision 1.3  2003/04/03 01:17:30  tcsmith
// Changed to support the rename of Category to Logger and Priority to
// LogLevel.
//

#include <log4cplus/layout.h>
#include <log4cplus/helpers/loglog.h>
#include <log4cplus/helpers/timehelper.h>
#include <log4cplus/helpers/stringhelper.h>
#include <log4cplus/spi/loggingevent.h>

#include <stdlib.h>
#include <exception>

using namespace std;
using namespace log4cplus;
using namespace log4cplus::helpers;
using namespace log4cplus::spi;


#define ESCAPE_CHAR LOG4CPLUS_TEXT('%')


namespace log4cplus {
    namespace pattern {

        /**
         * This is used by PatternConverter class to inform them how to format
         * their output.
         */
        struct FormattingInfo {
            int minLen;
            size_t maxLen;
            bool leftAlign;
            FormattingInfo() { reset(); }

            void reset();
            void dump(log4cplus::helpers::LogLog&);
        };



        /**
         * This is the base class of all "Converter" classes that format a
         * field of InternalLoggingEvent objects.  In fact, the PatternLayout
         * class simply uses an array of PatternConverter objects to format
         * and append a logging event.
         */
        class PatternConverter : protected log4cplus::helpers::LogLogUser {
        public:
            PatternConverter(const FormattingInfo& info);
            virtual ~PatternConverter() {}
            void formatAndAppend(log4cplus::tostream& output, 
                                 const InternalLoggingEvent& event);

        protected:
            virtual log4cplus::tstring convert(const InternalLoggingEvent& event) = 0;

        private:
            int minLen;
            size_t maxLen;
            bool leftAlign;
        };



        /**
         * This PatternConverter returns a constant string.
         */
        class LiteralPatternConverter : public PatternConverter {
        public:
            LiteralPatternConverter(const log4cplus::tstring& str);
            virtual log4cplus::tstring convert(const InternalLoggingEvent&) {
                return str;
            }

        private:
            log4cplus::tstring str;
        };



        /**
         * This PatternConverter is used to format most of the "simple" fields
         * found in the InternalLoggingEvent object.
         */
        class BasicPatternConverter : public PatternConverter {
        public:
            enum Type { THREAD_CONVERTER,
                        LOGLEVEL_CONVERTER,
                        NDC_CONVERTER,
                        MESSAGE_CONVERTER,
                        NEWLINE_CONVERTER,
                        FILE_CONVERTER,
                        LINE_CONVERTER,
                        FULL_LOCATION_CONVERTER };
            BasicPatternConverter(const FormattingInfo& info, Type type);
            virtual log4cplus::tstring convert(const InternalLoggingEvent& event);

        private:
          // Disable copy
            BasicPatternConverter(const BasicPatternConverter&);
            BasicPatternConverter& operator=(BasicPatternConverter&);
            
            LogLevelManager& llmCache;
            Type type;
        };



        /**
         * This PatternConverter is used to format the Logger field found in
         * the InternalLoggingEvent object.
         */
        class LoggerPatternConverter : public PatternConverter {
        public:
            LoggerPatternConverter(const FormattingInfo& info, int precision);
            virtual log4cplus::tstring convert(const InternalLoggingEvent& event);

        private:
            int precision;
        };



        /**
         * This PatternConverter is used to format the timestamp field found in
         * the InternalLoggingEvent object.  It will be formatted according to
         * the specified "pattern".
         */
        class DatePatternConverter : public PatternConverter {
        public:
            DatePatternConverter(const FormattingInfo& info, 
                                 const log4cplus::tstring& pattern, 
                                 bool use_gmtime);
            virtual log4cplus::tstring convert(const InternalLoggingEvent& event);

        private:
            bool use_gmtime;
            log4cplus::tstring format;
        };



        /**
         * This class parses a "pattern" string into an array of
         * PatternConverter objects.
         * <p>
         * @see PatternLayout for the formatting of the "pattern" string.
         */
        class PatternParser : protected log4cplus::helpers::LogLogUser {
        public:
            PatternParser(const log4cplus::tstring& pattern);
            std::vector<PatternConverter*> parse();

        private:
          // Types
            enum ParserState { LITERAL_STATE, 
                               CONVERTER_STATE,
                               DOT_STATE,
                               MIN_STATE,
                               MAX_STATE };

          // Methods
            log4cplus::tstring extractOption();
            int extractPrecisionOption();
            void finalizeConverter(log4cplus::tchar c);

          // Data
            log4cplus::tstring pattern;
            FormattingInfo formattingInfo;
            std::vector<PatternConverter*> list;
            ParserState state;
            tstring::size_type pos;
            log4cplus::tstring currentLiteral;
        };
    }
}
using namespace log4cplus::pattern;
typedef std::vector<log4cplus::pattern::PatternConverter*> PatternConverterList;



////////////////////////////////////////////////
// PatternConverter methods:
////////////////////////////////////////////////

void 
log4cplus::pattern::FormattingInfo::reset() {
    minLen = -1;
    maxLen = 0x7FFFFFFF;
    leftAlign = false;
}


void 
log4cplus::pattern::FormattingInfo::dump(log4cplus::helpers::LogLog& loglog) {
    log4cplus::tostringstream buf;
    buf << LOG4CPLUS_TEXT("min=") << minLen
        << LOG4CPLUS_TEXT(", max=") << maxLen
        << LOG4CPLUS_TEXT(", leftAlign=")
        << (buf ? LOG4CPLUS_TEXT("true") : LOG4CPLUS_TEXT("false"));
    loglog.debug(buf.str());
}




////////////////////////////////////////////////
// PatternConverter methods:
////////////////////////////////////////////////

log4cplus::pattern::PatternConverter::PatternConverter(const FormattingInfo& i)
{
    minLen = i.minLen;
    maxLen = i.maxLen;
    leftAlign = i.leftAlign;
}



void
log4cplus::pattern::PatternConverter::formatAndAppend
                     (log4cplus::tostream& output, const InternalLoggingEvent& event)
{
    log4cplus::tstring s = convert(event);
    size_t len = s.length();

    if(len > maxLen) {
        output << s.substr(len - maxLen);
    }
    else if(static_cast<int>(len) < minLen) {
        if(leftAlign) {
            output << s;
            output << log4cplus::tstring(minLen - len, LOG4CPLUS_TEXT(' '));
        }
        else {
            output << log4cplus::tstring(minLen - len, LOG4CPLUS_TEXT(' '));
            output << s;
        }
    }
    else {
        output << s;
    }
}



////////////////////////////////////////////////
// LiteralPatternConverter methods:
////////////////////////////////////////////////

log4cplus::pattern::LiteralPatternConverter::LiteralPatternConverter
                                                      (const log4cplus::tstring& str)
: PatternConverter(FormattingInfo()),
  str(str)
{
}



////////////////////////////////////////////////
// BasicPatternConverter methods:
////////////////////////////////////////////////

log4cplus::pattern::BasicPatternConverter::BasicPatternConverter
                                        (const FormattingInfo& info, Type type)
: PatternConverter(info),
  llmCache(getLogLevelManager()),
  type(type)
{
}



log4cplus::tstring
log4cplus::pattern::BasicPatternConverter::convert
                                            (const InternalLoggingEvent& event)
{
    switch(type) {
    case LOGLEVEL_CONVERTER: return llmCache.toString(event.getLogLevel());
    case NDC_CONVERTER:      return event.getNDC();
    case MESSAGE_CONVERTER:  return event.getMessage();
    case NEWLINE_CONVERTER:  return LOG4CPLUS_TEXT("\n");
    case FILE_CONVERTER:     return event.getFile();
    case THREAD_CONVERTER:   return event.getThread(); 

    case LINE_CONVERTER:
        {
            if(event.getLine() != -1) {
                return convertIntegerToString(event.getLine());
            }
            else {
                return log4cplus::tstring();
            }
        }

    case FULL_LOCATION_CONVERTER:
        {
            if(event.getFile().length() > 0) {
                return   event.getFile() 
                       + LOG4CPLUS_TEXT(":") 
                       + convertIntegerToString(event.getLine());
            }
            else {
                return LOG4CPLUS_TEXT(":");
            }
        }
    }

    return LOG4CPLUS_TEXT("INTERNAL LOG4CPLUS ERROR");
}



////////////////////////////////////////////////
// LoggerPatternConverter methods:
////////////////////////////////////////////////

log4cplus::pattern::LoggerPatternConverter::LoggerPatternConverter
                                    (const FormattingInfo& info, int precision)
: PatternConverter(info),
  precision(precision)
{
}



log4cplus::tstring
log4cplus::pattern::LoggerPatternConverter::convert
                                            (const InternalLoggingEvent& event)
{
    const log4cplus::tstring& name = event.getLoggerName();
    if (precision <= 0) {
        return name;
    }
    else {
        size_t len = name.length();

        // We substract 1 from 'len' when assigning to 'end' to avoid out of
        // bounds exception in return r.substring(end+1, len). This can happen
        // if precision is 1 and the logger name ends with a dot. 
        tstring::size_type end = len - 1;
        for(int i=precision; i>0; --i) {
            end = name.rfind(LOG4CPLUS_TEXT('.'), end - 1);
            if(end == tstring::npos) {
                return name;
            }
        }
        return name.substr(end + 1);
    }
}



////////////////////////////////////////////////
// DatePatternConverter methods:
////////////////////////////////////////////////


log4cplus::pattern::DatePatternConverter::DatePatternConverter
                                               (const FormattingInfo& info, 
                                                const log4cplus::tstring& pattern, 
                                                bool use_gmtime)
: PatternConverter(info),
  use_gmtime(use_gmtime),
  format(pattern)
{
}



log4cplus::tstring 
log4cplus::pattern::DatePatternConverter::convert
                                            (const InternalLoggingEvent& event)
{
    return event.getTimestamp().getFormattedTime(format, use_gmtime);
}




////////////////////////////////////////////////
// PatternParser methods:
////////////////////////////////////////////////

log4cplus::pattern::PatternParser::PatternParser(const log4cplus::tstring& pattern) 
: pattern(pattern), 
  state(LITERAL_STATE),
  pos(0)
{
}



log4cplus::tstring 
log4cplus::pattern::PatternParser::extractOption() 
{
    if (   (pos < pattern.length()) 
        && (pattern.at(pos) == LOG4CPLUS_TEXT('{'))) 
    {
        tstring::size_type end = pattern.find_first_of(LOG4CPLUS_TEXT('}'), pos);
        if (end > pos) {
            log4cplus::tstring r = pattern.substr(pos + 1, end - pos - 1);
            pos = end + 1;
            return r;
        }
    }

    return LOG4CPLUS_TEXT("");
}


int 
log4cplus::pattern::PatternParser::extractPrecisionOption() 
{
    log4cplus::tstring opt = extractOption();
    int r = 0;
    if(opt.length() > 0) {
        r = atoi(LOG4CPLUS_TSTRING_TO_STRING(opt).c_str());
    }
    return r;
}



PatternConverterList
log4cplus::pattern::PatternParser::parse() 
{
    tchar c;
    pos = 0;
    while(pos < pattern.length()) {
        c = pattern.at(pos++);
        switch (state) {
        case LITERAL_STATE :
            // In literal state, the last char is always a literal.
            if(pos == pattern.length()) {
                currentLiteral += c;
                continue;
            }
            if(c == ESCAPE_CHAR) {
                // peek at the next char. 
                switch (pattern.at(pos)) {
                case ESCAPE_CHAR:
                    currentLiteral += c;
                    pos++; // move pointer
                    break;
                default:
                    if(currentLiteral.length() != 0) {
                        list.push_back
                             (new LiteralPatternConverter(currentLiteral));
                        //getLogLog().debug("Parsed LITERAL converter: \"" 
                        //                  +currentLiteral+"\".");
                    }
                    currentLiteral.resize(0);
                    currentLiteral += c; // append %
                    state = CONVERTER_STATE;
                    formattingInfo.reset();
                }
            }
            else {
                currentLiteral += c;
            }
            break;

        case CONVERTER_STATE:
            currentLiteral += c;
            switch (c) {
            case LOG4CPLUS_TEXT('-'):
                formattingInfo.leftAlign = true;
                break;
            case LOG4CPLUS_TEXT('.'):
                state = DOT_STATE;
                break;
            default:
                if(c >= LOG4CPLUS_TEXT('0') && c <= LOG4CPLUS_TEXT('9')) {
                    formattingInfo.minLen = c - LOG4CPLUS_TEXT('0');
                    state = MIN_STATE;
                }
                else {
                    finalizeConverter(c);
                }
            } // switch
            break;

        case MIN_STATE:
            currentLiteral += c;
            if (c >= LOG4CPLUS_TEXT('0') && c <= LOG4CPLUS_TEXT('9')) {
                formattingInfo.minLen = formattingInfo.minLen * 10 + (c - LOG4CPLUS_TEXT('0'));
            }
            else if(c == LOG4CPLUS_TEXT('.')) {
                state = DOT_STATE;
            }
            else {
                finalizeConverter(c);
            }
            break;

        case DOT_STATE:
            currentLiteral += c;
            if(c >= LOG4CPLUS_TEXT('0') && c <= LOG4CPLUS_TEXT('9')) {
                formattingInfo.maxLen = c - LOG4CPLUS_TEXT('0');
                state = MAX_STATE;
            }
            else {
                log4cplus::tostringstream buf;
                buf << LOG4CPLUS_TEXT("Error occured in position ")
                    << pos
                    << LOG4CPLUS_TEXT(".\n Was expecting digit, instead got char \"")
                    << c
                    << LOG4CPLUS_TEXT("\".");
                getLogLog().error(buf.str());
                state = LITERAL_STATE;
            }
            break;

         case MAX_STATE:
            currentLiteral += c;
            if (c >= LOG4CPLUS_TEXT('0') && c <= LOG4CPLUS_TEXT('9'))
                formattingInfo.maxLen = formattingInfo.maxLen * 10 + (c - LOG4CPLUS_TEXT('0'));
            else {
                finalizeConverter(c);
                state = LITERAL_STATE;
            }
            break;
        } // end switch
    } // end while

    if(currentLiteral.length() != 0) {
        list.push_back(new LiteralPatternConverter(currentLiteral));
      //getLogLog().debug("Parsed LITERAL converter: \""+currentLiteral+"\".");
    }

    return list;
}



void
log4cplus::pattern::PatternParser::finalizeConverter(log4cplus::tchar c) 
{
    PatternConverter* pc = 0;
    switch (c) {
        case LOG4CPLUS_TEXT('c'):
            pc = new LoggerPatternConverter(formattingInfo, 
                                            extractPrecisionOption());
            getLogLog().debug( LOG4CPLUS_TEXT("LOGGER converter.") );
            formattingInfo.dump(getLogLog());      
            break;

        case LOG4CPLUS_TEXT('d'):
        case LOG4CPLUS_TEXT('D'):
            {
                log4cplus::tstring dOpt = extractOption();
                if(dOpt.length() == 0) {
                    dOpt = LOG4CPLUS_TEXT("%Y-%m-%d %H:%M:%S");
                }
                bool use_gmtime = c == LOG4CPLUS_TEXT('d');
                pc = new DatePatternConverter(formattingInfo, dOpt, use_gmtime);
                //if(use_gmtime) {
                //    getLogLog().debug("GMT DATE converter.");
                //}
                //else {
                //    getLogLog().debug("LOCAL DATE converter.");
                //}
                //formattingInfo.dump(getLogLog());      
            }
            break;

        case LOG4CPLUS_TEXT('F'):
            pc = new BasicPatternConverter
                          (formattingInfo, 
                           BasicPatternConverter::FILE_CONVERTER);
            //getLogLog().debug("FILE NAME converter.");
            //formattingInfo.dump(getLogLog());      
            break;

        case LOG4CPLUS_TEXT('l'):
            pc = new BasicPatternConverter
                          (formattingInfo, 
                           BasicPatternConverter::FULL_LOCATION_CONVERTER);
            //getLogLog().debug("FULL LOCATION converter.");
            //formattingInfo.dump(getLogLog());      
            break;

        case LOG4CPLUS_TEXT('L'):
            pc = new BasicPatternConverter
                          (formattingInfo, 
                           BasicPatternConverter::LINE_CONVERTER);
            //getLogLog().debug("LINE NUMBER converter.");
            //formattingInfo.dump(getLogLog());      
            break;

        case LOG4CPLUS_TEXT('m'):
            pc = new BasicPatternConverter
                          (formattingInfo, 
                           BasicPatternConverter::MESSAGE_CONVERTER);
            //getLogLog().debug("MESSAGE converter.");
            //formattingInfo.dump(getLogLog());      
            break;

        case LOG4CPLUS_TEXT('n'):
            pc = new BasicPatternConverter
                          (formattingInfo, 
                           BasicPatternConverter::NEWLINE_CONVERTER);
            //getLogLog().debug("MESSAGE converter.");
            //formattingInfo.dump(getLogLog());      
            break;

        case LOG4CPLUS_TEXT('p'):
            pc = new BasicPatternConverter
                          (formattingInfo, 
                           BasicPatternConverter::LOGLEVEL_CONVERTER);
            //getLogLog().debug("LOGLEVEL converter.");
            //formattingInfo.dump(getLogLog());
            break;

        case LOG4CPLUS_TEXT('t'):
            pc = new BasicPatternConverter
                          (formattingInfo, 
                           BasicPatternConverter::THREAD_CONVERTER);
            //getLogLog().debug("THREAD converter.");
            //formattingInfo.dump(getLogLog());      
            break;

        case LOG4CPLUS_TEXT('x'):
            pc = new BasicPatternConverter
                          (formattingInfo, 
                           BasicPatternConverter::NDC_CONVERTER);
            //getLogLog().debug("NDC converter.");      
            break;

        default:
            log4cplus::tostringstream buf;
            buf << LOG4CPLUS_TEXT("Unexpected char [")
                << c
                << LOG4CPLUS_TEXT("] at position ")
                << pos
                << LOG4CPLUS_TEXT(" in conversion patterrn.");
            getLogLog().error(buf.str());
            pc = new LiteralPatternConverter(currentLiteral);
    }

    currentLiteral.resize(0);
    list.push_back(pc);
    state = LITERAL_STATE;
    formattingInfo.reset();
}





////////////////////////////////////////////////
// PatternLayout methods:
////////////////////////////////////////////////

PatternLayout::PatternLayout(const log4cplus::tstring& pattern)
{
    init(pattern);
}


PatternLayout::PatternLayout(const log4cplus::helpers::Properties& properties)
{
    bool hasPattern = properties.exists( LOG4CPLUS_TEXT("Pattern") );
    bool hasConversionPattern = properties.exists( LOG4CPLUS_TEXT("ConversionPattern") );
    
    if(hasPattern) {
        getLogLog().warn( LOG4CPLUS_TEXT("PatternLayout- the \"Pattern\" property has been deprecated.  Use \"ConversionPattern\" instead."));
    }
    
    if(hasConversionPattern) {
        init(properties.getProperty( LOG4CPLUS_TEXT("ConversionPattern") ));
    }
    else if(hasPattern) {
        init(properties.getProperty( LOG4CPLUS_TEXT("Pattern") ));
    }
    else {
        throw std::runtime_error("ConversionPattern not specified in properties");
    }

}


void
PatternLayout::init(const log4cplus::tstring& pattern)
{
    this->pattern = pattern;
    this->parsedPattern = PatternParser(pattern).parse();

    // Let's validate that our parser didn't give us any NULLs.  If it did,
    // we will convert them to a valid PatternConverter that does nothing so
    // at least we don't core.
    for(PatternConverterList::iterator it=parsedPattern.begin(); 
        it!=parsedPattern.end(); 
        ++it)
    {
        if( (*it) == 0 ) {
            getLogLog().error(LOG4CPLUS_TEXT("Parsed Pattern created a NULL PatternConverter"));
            (*it) = new LiteralPatternConverter( LOG4CPLUS_TEXT("") );
        }
    }
    if(parsedPattern.size() == 0) {
        getLogLog().warn(LOG4CPLUS_TEXT("PatternLayout pattern is empty.  Using default..."));
        parsedPattern.push_back
           (new BasicPatternConverter(FormattingInfo(), 
                                      BasicPatternConverter::MESSAGE_CONVERTER));
    }
}



PatternLayout::~PatternLayout()
{
    for(PatternConverterList::iterator it=parsedPattern.begin(); 
        it!=parsedPattern.end(); 
        ++it)
    {
        delete (*it);
    }
}



void
PatternLayout::formatAndAppend(log4cplus::tostream& output, 
                               const InternalLoggingEvent& event)
{
    for(PatternConverterList::iterator it=parsedPattern.begin(); 
        it!=parsedPattern.end(); 
        ++it)
    {
        (*it)->formatAndAppend(output, event);
    }
}




syntax highlighted by Code2HTML, v. 0.9.1