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 if (value != null) { 101 Node innerNode = tokenizeAndParseString(value); 102 compileNode(innerNode, stringBuilder, cycleCheckStack); 103 cycleCheckStack.pop(); 104 return; 105 } 106 107 if (n.defaultPart == null) { 108 stringBuilder.append(key + CoreConstants.UNDEFINED_PROPERTY_SUFFIX); 109 cycleCheckStack.pop(); 110 return; 111 } 112 113 Node defaultPart = (Node) n.defaultPart; 114 StringBuilder defaultPartBuffer = new StringBuilder(); 115 compileNode(defaultPart, defaultPartBuffer, cycleCheckStack); 116 cycleCheckStack.pop(); 117 String defaultVal = defaultPartBuffer.toString(); 118 stringBuilder.append(defaultVal); 119 } 120 121 private String lookupKey(String key) { 122 String value = propertyContainer0.getProperty(key); 123 if (value != null) 124 return value; 125 126 if (propertyContainer1 != null) { 127 value = propertyContainer1.getProperty(key); 128 if (value != null) 129 return value; 130 } 131 132 value = OptionHelper.getSystemProperty(key, null); 133 if (value != null) 134 return value; 135 136 value = OptionHelper.getEnv(key); 137 if (value != null) { 138 return value; 139 } 140 141 return null; 142 } 143 144 private void handleLiteral(Node n, StringBuilder stringBuilder) { 145 stringBuilder.append((String) n.payload); 146 } 147 148 private String variableNodeValue(Node variableNode) { 149 Node payload = (Node) variableNode.payload; 150 if(payload == null) { 151 return CoreConstants.EMPTY_STRING; 152 } 153 154 if(payload.type == Type.LITERAL) { 155 return (String) payload.payload; 156 } 157 158 if(payload.type == Type.VARIABLE) { 159 return " ? " + variableNodeValue(payload); 160 } 161 throw new IllegalStateException("unreachable code"); 162 } 163 164 private String constructRecursionErrorMessage(Stack<Node> recursionNodes) { 165 StringBuilder errorBuilder = new StringBuilder(CIRCULAR_VARIABLE_REFERENCE_DETECTED); 166 167 for (Node stackNode : recursionNodes) { 168 errorBuilder.append("${").append(variableNodeValue(stackNode)).append("}"); 169 if (recursionNodes.lastElement() != stackNode) { 170 errorBuilder.append(" --> "); 171 } 172 } 173 errorBuilder.append("]"); 174 return errorBuilder.toString(); 175 } 176 177 /** 178 * Determine if a node has already been visited already by checking the 179 * cycleDetectionStack for its existence. This method is used -- rather than 180 * Stack.contains() -- because we want to ignore the Node's 'next' attribute 181 * when comparing for equality. 182 */ 183 private boolean haveVisitedNodeAlready(Node node, Stack<Node> cycleDetectionStack) { 184 for (Node cycleNode : cycleDetectionStack) { 185 if (equalNodes(node, cycleNode)) { 186 return true; 187 } 188 } 189 return false; 190 } 191 192 private boolean equalNodes(Node node1, Node node2) { 193 if (node1.type != null && !node1.type.equals(node2.type)) 194 return false; 195 if (node1.payload != null && !node1.payload.equals(node2.payload)) 196 return false; 197 if (node1.defaultPart != null && !node1.defaultPart.equals(node2.defaultPart)) 198 return false; 199 200 return true; 201 } 202 203}