View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.subst;
15  
16  import ch.qos.logback.core.CoreConstants;
17  import ch.qos.logback.core.spi.PropertyContainer;
18  import ch.qos.logback.core.spi.ScanException;
19  import ch.qos.logback.core.util.OptionHelper;
20  
21  import java.util.List;
22  import java.util.Stack;
23  
24  /**
25   * Compiles a previously parsed Node chain into a String.
26   *
27   * @author Ceki Gülcü
28   */
29  public class NodeToStringTransformer {
30  
31      final Node node;
32      final PropertyContainer propertyContainer0;
33      final PropertyContainer propertyContainer1;
34  
35      public NodeToStringTransformer(Node node, PropertyContainer propertyContainer0, PropertyContainer propertyContainer1) {
36          this.node = node;
37          this.propertyContainer0 = propertyContainer0;
38          this.propertyContainer1 = propertyContainer1;
39      }
40  
41      public NodeToStringTransformer(Node node, PropertyContainer propertyContainer0) {
42          this(node, propertyContainer0, null);
43      }
44  
45      public static String substituteVariable(String input, PropertyContainer pc0, PropertyContainer pc1) throws ScanException {
46          Node node = tokenizeAndParseString(input);
47          NodeToStringTransformer nodeToStringTransformer = new NodeToStringTransformer(node, pc0, pc1);
48          return nodeToStringTransformer.transform();
49      }
50  
51      private static Node tokenizeAndParseString(String value) throws ScanException {
52          Tokenizer tokenizer = new Tokenizer(value);
53          List<Token> tokens = tokenizer.tokenize();
54          Parser parser = new Parser(tokens);
55          return parser.parse();
56      }
57  
58      public String transform() throws ScanException {
59          StringBuilder stringBuilder = new StringBuilder();
60          compileNode(node, stringBuilder, new Stack<Node>());
61          return stringBuilder.toString();
62      }
63  
64      private void compileNode(Node inputNode, StringBuilder stringBuilder, Stack<Node> cycleCheckStack) throws ScanException {
65          Node n = inputNode;
66          while (n != null) {
67              switch (n.type) {
68              case LITERAL:
69                  handleLiteral(n, stringBuilder);
70                  break;
71              case VARIABLE:
72                  handleVariable(n, stringBuilder, cycleCheckStack);
73                  break;
74              }
75              n = n.next;
76          }
77      }
78  
79      private void handleVariable(Node n, StringBuilder stringBuilder, Stack<Node> cycleCheckStack) throws ScanException {
80  
81          // Check for recursion
82          if (haveVisitedNodeAlready(n, cycleCheckStack)) {
83              cycleCheckStack.push(n);
84              String error = constructRecursionErrorMessage(cycleCheckStack);
85              throw new IllegalArgumentException(error);
86          }
87          cycleCheckStack.push(n);
88  
89          StringBuilder keyBuffer = new StringBuilder();
90          Node payload = (Node) n.payload;
91          compileNode(payload, keyBuffer, cycleCheckStack);
92          String key = keyBuffer.toString();
93          String value = lookupKey(key);
94  
95          if (value != null) {
96              Node innerNode = tokenizeAndParseString(value);
97              compileNode(innerNode, stringBuilder, cycleCheckStack);
98              cycleCheckStack.pop();
99              return;
100         }
101 
102         if (n.defaultPart == null) {
103             stringBuilder.append(key + CoreConstants.UNDEFINED_PROPERTY_SUFFIX);
104             cycleCheckStack.pop();
105             return;
106         }
107 
108         Node defaultPart = (Node) n.defaultPart;
109         StringBuilder defaultPartBuffer = new StringBuilder();
110         compileNode(defaultPart, defaultPartBuffer, cycleCheckStack);
111         cycleCheckStack.pop();
112         String defaultVal = defaultPartBuffer.toString();
113         stringBuilder.append(defaultVal);
114     }
115 
116     private String lookupKey(String key) {
117         String value = propertyContainer0.getProperty(key);
118         if (value != null)
119             return value;
120 
121         if (propertyContainer1 != null) {
122             value = propertyContainer1.getProperty(key);
123             if (value != null)
124                 return value;
125         }
126 
127         value = OptionHelper.getSystemProperty(key, null);
128         if (value != null)
129             return value;
130 
131         value = OptionHelper.getEnv(key);
132         if (value != null) {
133             return value;
134         }
135 
136         return null;
137     }
138 
139     private void handleLiteral(Node n, StringBuilder stringBuilder) {
140         stringBuilder.append((String) n.payload);
141     }
142 
143     private String variableNodeValue(Node variableNode) {
144         Node literalPayload = (Node) variableNode.payload;
145         return (String) literalPayload.payload;
146     }
147 
148     private String constructRecursionErrorMessage(Stack<Node> recursionNodes) {
149         StringBuilder errorBuilder = new StringBuilder("Circular variable reference detected while parsing input [");
150 
151         for (Node stackNode : recursionNodes) {
152             errorBuilder.append("${").append(variableNodeValue(stackNode)).append("}");
153             if (recursionNodes.lastElement() != stackNode) {
154                 errorBuilder.append(" --> ");
155             }
156         }
157         errorBuilder.append("]");
158         return errorBuilder.toString();
159     }
160 
161     /**
162      * Determine if a node has already been visited already by checking the cycleDetectionStack
163      * for it's existence. This method is used -- rather than Stack.contains() -- because
164      * we want to ignore the Node's 'next' attribute when comparing for equality.
165      */
166     private boolean haveVisitedNodeAlready(Node node, Stack<Node> cycleDetectionStack) {
167         for (Node cycleNode : cycleDetectionStack) {
168             if (equalNodes(node, cycleNode)) {
169                 return true;
170             }
171         }
172         return false;
173     }
174 
175     private boolean equalNodes(Node node1, Node node2) {
176         if (node1.type != null && !node1.type.equals(node2.type))
177             return false;
178         if (node1.payload != null && !node1.payload.equals(node2.payload))
179             return false;
180         if (node1.defaultPart != null && !node1.defaultPart.equals(node2.defaultPart))
181             return false;
182 
183         return true;
184     }
185 
186 }