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.subst;
015
016import ch.qos.logback.core.CoreConstants;
017import ch.qos.logback.core.spi.PropertyContainer;
018import ch.qos.logback.core.spi.ScanException;
019import ch.qos.logback.core.subst.Node.Type;
020import ch.qos.logback.core.util.OptionHelper;
021
022import java.util.List;
023import java.util.Stack;
024
025/**
026 * Compiles a previously parsed Node chain into a String.
027 *
028 * @author Ceki Gülcü
029 */
030public class NodeToStringTransformer {
031
032    public static final String CIRCULAR_VARIABLE_REFERENCE_DETECTED = "Circular variable reference detected while parsing input [";
033    final Node node;
034    final PropertyContainer propertyContainer0;
035    final PropertyContainer propertyContainer1;
036
037    public NodeToStringTransformer(Node node, PropertyContainer propertyContainer0,
038            PropertyContainer propertyContainer1) {
039        this.node = node;
040        this.propertyContainer0 = propertyContainer0;
041        this.propertyContainer1 = propertyContainer1;
042    }
043
044    public NodeToStringTransformer(Node node, PropertyContainer propertyContainer0) {
045        this(node, propertyContainer0, null);
046    }
047
048    public static String substituteVariable(String input, PropertyContainer pc0, PropertyContainer pc1)
049            throws ScanException {
050        Node node = tokenizeAndParseString(input);
051        NodeToStringTransformer nodeToStringTransformer = new NodeToStringTransformer(node, pc0, pc1);
052        return nodeToStringTransformer.transform();
053    }
054
055    private static Node tokenizeAndParseString(String value) throws ScanException {
056        Tokenizer tokenizer = new Tokenizer(value);
057        List<Token> tokens = tokenizer.tokenize();
058        Parser parser = new Parser(tokens);
059        return parser.parse();
060    }
061
062    public String transform() throws ScanException {
063        StringBuilder stringBuilder = new StringBuilder();
064        compileNode(node, stringBuilder, new Stack<Node>());
065        return stringBuilder.toString();
066    }
067
068    private void compileNode(Node inputNode, StringBuilder stringBuilder, Stack<Node> cycleCheckStack)
069            throws ScanException {
070        Node n = inputNode;
071        while (n != null) {
072            switch (n.type) {
073            case LITERAL:
074                handleLiteral(n, stringBuilder);
075                break;
076            case VARIABLE:
077                handleVariable(n, stringBuilder, cycleCheckStack);
078                break;
079            }
080            n = n.next;
081        }
082    }
083
084    private void handleVariable(Node n, StringBuilder stringBuilder, Stack<Node> cycleCheckStack) throws ScanException {
085
086        // Check for recursion
087        if (haveVisitedNodeAlready(n, cycleCheckStack)) {
088            cycleCheckStack.push(n);
089            String error = constructRecursionErrorMessage(cycleCheckStack);
090            throw new IllegalArgumentException(error);
091        }
092        cycleCheckStack.push(n);
093
094        StringBuilder keyBuffer = new StringBuilder();
095        Node payload = (Node) n.payload;
096        compileNode(payload, keyBuffer, cycleCheckStack);
097        String key = keyBuffer.toString();
098        String value = lookupKey(key);
099
100        // empty values are considered valid
101        if (value != null) {
102            Node innerNode = tokenizeAndParseString(value);
103            compileNode(innerNode, stringBuilder, cycleCheckStack);
104            cycleCheckStack.pop();
105            return;
106        }
107
108        // empty default literal is a valid value
109        if (n.defaultPart == null) {
110            stringBuilder.append(key + CoreConstants.UNDEFINED_PROPERTY_SUFFIX);
111            cycleCheckStack.pop();
112            return;
113        }
114
115        Node defaultPart = (Node) n.defaultPart;
116        StringBuilder defaultPartBuffer = new StringBuilder();
117        compileNode(defaultPart, defaultPartBuffer, cycleCheckStack);
118        cycleCheckStack.pop();
119        String defaultVal = defaultPartBuffer.toString();
120        stringBuilder.append(defaultVal);
121    }
122
123    private String lookupKey(String key) {
124        String value = propertyContainer0.getProperty(key);
125        if (value != null)
126            return value;
127
128        if (propertyContainer1 != null) {
129            value = propertyContainer1.getProperty(key);
130            if (value != null)
131                return value;
132        }
133
134        value = OptionHelper.getSystemProperty(key, null);
135        if (value != null)
136            return value;
137
138        value = OptionHelper.getEnv(key);
139        if (value != null) {
140            return value;
141        }
142
143        return null;
144    }
145
146    private void handleLiteral(Node n, StringBuilder stringBuilder) {
147        stringBuilder.append((String) n.payload);
148    }
149
150    private String variableNodeValue(Node variableNode) {
151        Node payload = (Node) variableNode.payload;
152        if(payload == null) {
153            return CoreConstants.EMPTY_STRING;
154        }
155        
156        if(payload.type == Type.LITERAL) {
157            return (String) payload.payload;
158        }
159        
160        if(payload.type == Type.VARIABLE) {
161            return " ? " + variableNodeValue(payload);
162        }
163        throw new IllegalStateException("unreachable code");
164    }
165
166    private String constructRecursionErrorMessage(Stack<Node> recursionNodes) {
167        StringBuilder errorBuilder = new StringBuilder(CIRCULAR_VARIABLE_REFERENCE_DETECTED);
168
169        for (Node stackNode : recursionNodes) {
170            errorBuilder.append("${").append(variableNodeValue(stackNode)).append("}");
171            if (recursionNodes.lastElement() != stackNode) {
172                errorBuilder.append(" --> ");
173            }
174        }
175        errorBuilder.append("]");
176        return errorBuilder.toString();
177    }
178
179    /**
180     * Determine if a node has already been visited already by checking the
181     * cycleDetectionStack for its existence. This method is used -- rather than
182     * Stack.contains() -- because we want to ignore the Node's 'next' attribute
183     * when comparing for equality.
184     */
185    private boolean haveVisitedNodeAlready(Node node, Stack<Node> cycleDetectionStack) {
186        for (Node cycleNode : cycleDetectionStack) {
187            if (equalNodes(node, cycleNode)) {
188                return true;
189            }
190        }
191        return false;
192    }
193
194    private boolean equalNodes(Node node1, Node node2) {
195        if (node1.type != null && !node1.type.equals(node2.type))
196            return false;
197        if (node1.payload != null && !node1.payload.equals(node2.payload))
198            return false;
199        if (node1.defaultPart != null && !node1.defaultPart.equals(node2.defaultPart))
200            return false;
201
202        return true;
203    }
204
205}