1
2
3
4
5
6
7
8
9
10
11
12
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.subst.Node.Type;
20 import ch.qos.logback.core.util.OptionHelper;
21
22 import java.util.List;
23 import java.util.Stack;
24
25
26
27
28
29
30 public class NodeToStringTransformer {
31
32 public static final String CIRCULAR_VARIABLE_REFERENCE_DETECTED = "Circular variable reference detected while parsing input [";
33 final Node node;
34 final PropertyContainer propertyContainer0;
35 final PropertyContainer propertyContainer1;
36
37 public NodeToStringTransformer(Node node, PropertyContainer propertyContainer0,
38 PropertyContainer propertyContainer1) {
39 this.node = node;
40 this.propertyContainer0 = propertyContainer0;
41 this.propertyContainer1 = propertyContainer1;
42 }
43
44 public NodeToStringTransformer(Node node, PropertyContainer propertyContainer0) {
45 this(node, propertyContainer0, null);
46 }
47
48 public static String substituteVariable(String input, PropertyContainer pc0, PropertyContainer pc1)
49 throws ScanException {
50 Node node = tokenizeAndParseString(input);
51 NodeToStringTransformer nodeToStringTransformer = new NodeToStringTransformer(node, pc0, pc1);
52 return nodeToStringTransformer.transform();
53 }
54
55 private static Node tokenizeAndParseString(String value) throws ScanException {
56 Tokenizer tokenizer = new Tokenizer(value);
57 List<Token> tokens = tokenizer.tokenize();
58 Parser parser = new Parser(tokens);
59 return parser.parse();
60 }
61
62 public String transform() throws ScanException {
63 StringBuilder stringBuilder = new StringBuilder();
64 compileNode(node, stringBuilder, new Stack<Node>());
65 return stringBuilder.toString();
66 }
67
68 private void compileNode(Node inputNode, StringBuilder stringBuilder, Stack<Node> cycleCheckStack)
69 throws ScanException {
70 Node n = inputNode;
71 while (n != null) {
72 switch (n.type) {
73 case LITERAL:
74 handleLiteral(n, stringBuilder);
75 break;
76 case VARIABLE:
77 handleVariable(n, stringBuilder, cycleCheckStack);
78 break;
79 }
80 n = n.next;
81 }
82 }
83
84 private void handleVariable(Node n, StringBuilder stringBuilder, Stack<Node> cycleCheckStack) throws ScanException {
85
86
87 if (haveVisitedNodeAlready(n, cycleCheckStack)) {
88 cycleCheckStack.push(n);
89 String error = constructRecursionErrorMessage(cycleCheckStack);
90 throw new IllegalArgumentException(error);
91 }
92 cycleCheckStack.push(n);
93
94 StringBuilder keyBuffer = new StringBuilder();
95 Node payload = (Node) n.payload;
96 compileNode(payload, keyBuffer, cycleCheckStack);
97 String key = keyBuffer.toString();
98 String value = lookupKey(key);
99
100
101 if (value != null) {
102 Node innerNode = tokenizeAndParseString(value);
103 compileNode(innerNode, stringBuilder, cycleCheckStack);
104 cycleCheckStack.pop();
105 return;
106 }
107
108
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
181
182
183
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 }