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}