001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.pattern.parser;
015
016import java.util.ArrayList;
017import java.util.List;
018
019import ch.qos.logback.core.pattern.util.AsIsEscapeUtil;
020import ch.qos.logback.core.pattern.util.IEscapeUtil;
021
022import static ch.qos.logback.core.CoreConstants.CURLY_RIGHT;
023
024import static ch.qos.logback.core.CoreConstants.ESCAPE_CHAR;
025import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
026import static ch.qos.logback.core.CoreConstants.SINGLE_QUOTE_CHAR;
027import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
028
029import ch.qos.logback.core.pattern.parser.TokenStream.TokenizerState;
030import ch.qos.logback.core.spi.ScanException;
031
032public class OptionTokenizer {
033
034    private final static int EXPECTING_STATE = 0;
035    private final static int RAW_COLLECTING_STATE = 1;
036    private final static int QUOTED_COLLECTING_STATE = 2;
037
038    final IEscapeUtil escapeUtil;
039    final TokenStream tokenStream;
040    final String pattern;
041    final int patternLength;
042
043    char quoteChar;
044    int state = EXPECTING_STATE;
045
046    OptionTokenizer(TokenStream tokenStream) {
047        this(tokenStream, new AsIsEscapeUtil());
048    }
049
050    OptionTokenizer(TokenStream tokenStream, IEscapeUtil escapeUtil) {
051        this.tokenStream = tokenStream;
052        this.pattern = tokenStream.pattern;
053        this.patternLength = tokenStream.patternLength;
054        this.escapeUtil = escapeUtil;
055    }
056
057    void tokenize(char firstChar, List<Token> tokenList) throws ScanException {
058        StringBuffer buf = new StringBuffer();
059        List<String> optionList = new ArrayList<String>();
060        char c = firstChar;
061
062        while (tokenStream.pointer < patternLength) {
063            switch (state) {
064            case EXPECTING_STATE:
065                switch (c) {
066                case ' ':
067                case '\t':
068                case '\r':
069                case '\n':
070                case COMMA_CHAR:
071                    break;
072                case SINGLE_QUOTE_CHAR:
073                case DOUBLE_QUOTE_CHAR:
074                    state = QUOTED_COLLECTING_STATE;
075                    quoteChar = c;
076                    break;
077                case CURLY_RIGHT:
078                    emitOptionToken(tokenList, optionList);
079                    return;
080                default:
081                    buf.append(c);
082                    state = RAW_COLLECTING_STATE;
083                }
084                break;
085            case RAW_COLLECTING_STATE:
086                switch (c) {
087                case COMMA_CHAR:
088                    optionList.add(buf.toString().trim());
089                    buf.setLength(0);
090                    state = EXPECTING_STATE;
091                    break;
092                case CURLY_RIGHT:
093                    optionList.add(buf.toString().trim());
094                    emitOptionToken(tokenList, optionList);
095                    return;
096                default:
097                    buf.append(c);
098                }
099                break;
100            case QUOTED_COLLECTING_STATE:
101                if (c == quoteChar) {
102                    optionList.add(buf.toString());
103                    buf.setLength(0);
104                    state = EXPECTING_STATE;
105                } else if (c == ESCAPE_CHAR) {
106                    escape(String.valueOf(quoteChar), buf);
107                } else {
108                    buf.append(c);
109                }
110
111                break;
112            }
113
114            c = pattern.charAt(tokenStream.pointer);
115            tokenStream.pointer++;
116        }
117
118        // EOS
119        if (c == CURLY_RIGHT) {
120            if (state == EXPECTING_STATE) {
121                emitOptionToken(tokenList, optionList);
122            } else if (state == RAW_COLLECTING_STATE) {
123                optionList.add(buf.toString().trim());
124                emitOptionToken(tokenList, optionList);
125            } else {
126                throw new ScanException("Unexpected end of pattern string in OptionTokenizer");
127            }
128        } else {
129            throw new ScanException("Unexpected end of pattern string in OptionTokenizer");
130        }
131    }
132
133    void emitOptionToken(List<Token> tokenList, List<String> optionList) {
134        tokenList.add(new Token(Token.OPTION, optionList));
135        tokenStream.state = TokenizerState.LITERAL_STATE;
136    }
137
138    void escape(String escapeChars, StringBuffer buf) {
139        if ((tokenStream.pointer < patternLength)) {
140            char next = pattern.charAt(tokenStream.pointer++);
141            escapeUtil.escape(escapeChars, buf, next, tokenStream.pointer);
142        }
143    }
144}