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.HashMap;
017import java.util.List;
018import java.util.Map;
019import java.util.function.Supplier;
020
021import ch.qos.logback.core.CoreConstants;
022import ch.qos.logback.core.pattern.*;
023import ch.qos.logback.core.pattern.util.IEscapeUtil;
024import ch.qos.logback.core.pattern.util.RegularEscapeUtil;
025import ch.qos.logback.core.spi.ContextAwareBase;
026import ch.qos.logback.core.spi.ScanException;
027
028// ~=lambda
029// E = TE|T
030
031// Left factorization
032// E = T(E|~)
033// Eopt = E|~
034// replace E|~ with Eopt in E
035// E = TEopt
036
037// T = LITERAL | '%' C | '%' FORMAT_MODIFIER C
038// C = SIMPLE_KEYWORD OPTION | COMPOSITE_KEYWORD COMPOSITE
039// OPTION = {...} | ~
040// COMPOSITE = E ')' OPTION
041
042public class Parser<E> extends ContextAwareBase {
043
044    public final static String MISSING_RIGHT_PARENTHESIS = CoreConstants.CODES_URL + "#missingRightParenthesis";
045    public final static Map<String, Supplier<DynamicConverter>> DEFAULT_COMPOSITE_CONVERTER_MAP = new HashMap<>();
046    public final static String REPLACE_CONVERTER_WORD = "replace";
047    static {
048        DEFAULT_COMPOSITE_CONVERTER_MAP.put(Token.BARE_COMPOSITE_KEYWORD_TOKEN.getValue().toString(),
049                IdentityCompositeConverter::new);
050        DEFAULT_COMPOSITE_CONVERTER_MAP.put(REPLACE_CONVERTER_WORD, ReplacingCompositeConverter::new);
051    }
052
053    final List<Token> tokenList;
054    int pointer = 0;
055
056    Parser(TokenStream ts) throws ScanException {
057        this.tokenList = ts.tokenize();
058    }
059
060    public Parser(String pattern) throws ScanException {
061        this(pattern, new RegularEscapeUtil());
062    }
063
064    public Parser(String pattern, IEscapeUtil escapeUtil) throws ScanException {
065        try {
066            TokenStream ts = new TokenStream(pattern, escapeUtil);
067            this.tokenList = ts.tokenize();
068        } catch (IllegalArgumentException npe) {
069            throw new ScanException("Failed to initialize Parser", npe);
070        }
071    }
072
073    /**
074     * When the parsing step is done, the Node list can be transformed into a
075     * converter chain.
076     *
077     * @param top
078     * @param converterMap
079     * @return
080     */
081    public Converter<E> compile(final Node top, Map<String, Supplier<DynamicConverter>> converterMap) {
082        Compiler<E> compiler = new Compiler<E>(top, converterMap);
083        compiler.setContext(context);
084        // compiler.setStatusManager(statusManager);
085        return compiler.compile();
086    }
087
088    public Node parse() throws ScanException {
089        return E();
090    }
091
092    // E = TEopt
093    Node E() throws ScanException {
094        Node t = T();
095        if (t == null) {
096            return null;
097        }
098        Node eOpt = Eopt();
099        if (eOpt != null) {
100            t.setNext(eOpt);
101        }
102        return t;
103    }
104
105    // Eopt = E|~
106    Node Eopt() throws ScanException {
107        // System.out.println("in Eopt()");
108        Token next = getCurentToken();
109        // System.out.println("Current token is " + next);
110        if (next == null) {
111            return null;
112        } else {
113            return E();
114        }
115    }
116
117    // T = LITERAL | '%' C | '%' FORMAT_MODIFIER C
118    Node T() throws ScanException {
119        Token t = getCurentToken();
120        expectNotNull(t, "a LITERAL or '%'");
121
122        switch (t.getType()) {
123        case Token.LITERAL:
124            advanceTokenPointer();
125            return new Node(Node.LITERAL, t.getValue());
126        case Token.PERCENT:
127            advanceTokenPointer();
128            // System.out.println("% token found");
129            FormatInfo fi;
130            Token u = getCurentToken();
131            FormattingNode c;
132            expectNotNull(u, "a FORMAT_MODIFIER, SIMPLE_KEYWORD or COMPOUND_KEYWORD");
133            if (u.getType() == Token.FORMAT_MODIFIER) {
134                fi = FormatInfo.valueOf((String) u.getValue());
135                advanceTokenPointer();
136                c = C();
137                c.setFormatInfo(fi);
138            } else {
139                c = C();
140            }
141            return c;
142
143        default:
144            return null;
145
146        }
147
148    }
149
150    FormattingNode C() throws ScanException {
151        Token t = getCurentToken();
152        // System.out.println("in C()");
153        // System.out.println("Current token is " + t);
154        expectNotNull(t, "a LEFT_PARENTHESIS or KEYWORD");
155        int type = t.getType();
156        switch (type) {
157        case Token.SIMPLE_KEYWORD:
158            return SINGLE();
159        case Token.COMPOSITE_KEYWORD:
160            advanceTokenPointer();
161            return COMPOSITE(t.getValue().toString());
162        default:
163            throw new IllegalStateException("Unexpected token " + t);
164        }
165    }
166
167    FormattingNode SINGLE() throws ScanException {
168        // System.out.println("in SINGLE()");
169        Token t = getNextToken();
170        // System.out.println("==" + t);
171        SimpleKeywordNode keywordNode = new SimpleKeywordNode(t.getValue());
172
173        Token ot = getCurentToken();
174        if (ot != null && ot.getType() == Token.OPTION) {
175            List<String> optionList = ot.getOptionsList();
176            keywordNode.setOptions(optionList);
177            advanceTokenPointer();
178        }
179        return keywordNode;
180    }
181
182    FormattingNode COMPOSITE(String keyword) throws ScanException {
183        CompositeNode compositeNode = new CompositeNode(keyword);
184
185        Node childNode = E();
186        compositeNode.setChildNode(childNode);
187
188        Token t = getNextToken();
189
190        if (t == null || t.getType() != Token.RIGHT_PARENTHESIS) {
191            String msg = "Expecting RIGHT_PARENTHESIS token but got " + t;
192            addError(msg);
193            addError("See also " + MISSING_RIGHT_PARENTHESIS);
194            throw new ScanException(msg);
195        }
196        Token ot = getCurentToken();
197        if (ot != null && ot.getType() == Token.OPTION) {
198            List<String> optionList = ot.getOptionsList();
199            compositeNode.setOptions(optionList);
200            advanceTokenPointer();
201        }
202        return compositeNode;
203    }
204
205    Token getNextToken() {
206        if (pointer < tokenList.size()) {
207            return (Token) tokenList.get(pointer++);
208        }
209        return null;
210    }
211
212    Token getCurentToken() {
213        if (pointer < tokenList.size()) {
214            return (Token) tokenList.get(pointer);
215        }
216        return null;
217    }
218
219    void advanceTokenPointer() {
220        pointer++;
221    }
222
223    void expectNotNull(Token t, String expected) {
224        if (t == null) {
225            throw new IllegalStateException("All tokens consumed but was expecting " + expected);
226        }
227    }
228
229    // public void setStatusManager(StatusManager statusManager) {
230    // this.statusManager = statusManager;
231    // }
232}