001 // Copyright 2004, 2005 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.hivemind.impl; 016 017 import org.apache.commons.logging.Log; 018 import org.apache.commons.logging.LogFactory; 019 import org.apache.hivemind.ErrorHandler; 020 import org.apache.hivemind.Location; 021 import org.apache.hivemind.SymbolSource; 022 023 /** 024 * A simple parser used to identify symbols in a string and expand them via a 025 * {@link org.apache.hivemind.SymbolSource}. 026 * 027 * @author Howard Lewis Ship 028 */ 029 public class SymbolExpander 030 { 031 private ErrorHandler _errorHandler; 032 033 private SymbolSource _source; 034 035 public SymbolExpander(ErrorHandler handler, SymbolSource source) 036 { 037 _errorHandler = handler; 038 _source = source; 039 } 040 041 private static final Log LOG = LogFactory.getLog(SymbolExpander.class); 042 043 private static final int STATE_START = 0; 044 045 private static final int STATE_DOLLAR = 1; 046 047 private static final int STATE_COLLECT_SYMBOL_NAME = 2; 048 049 /** 050 * <p> 051 * Identifies symbols in the text and expands them, using the {@link SymbolSource}. Returns the 052 * modified text. May return text if text does not contain any symbols. 053 * 054 * @param text 055 * the text to scan 056 * @param location 057 * the location to report errors (undefined symbols) 058 */ 059 public String expandSymbols(String text, Location location) 060 { 061 StringBuffer result = new StringBuffer(text.length()); 062 char[] buffer = text.toCharArray(); 063 int state = STATE_START; 064 int blockStart = 0; 065 int blockLength = 0; 066 int symbolStart = -1; 067 int symbolLength = 0; 068 int i = 0; 069 int braceDepth = 0; 070 boolean anySymbols = false; 071 072 while (i < buffer.length) 073 { 074 char ch = buffer[i]; 075 076 switch (state) 077 { 078 case STATE_START: 079 080 if (ch == '$') 081 { 082 state = STATE_DOLLAR; 083 i++; 084 continue; 085 } 086 087 blockLength++; 088 i++; 089 continue; 090 091 case STATE_DOLLAR: 092 093 if (ch == '{') 094 { 095 state = STATE_COLLECT_SYMBOL_NAME; 096 i++; 097 098 symbolStart = i; 099 symbolLength = 0; 100 braceDepth = 1; 101 102 continue; 103 } 104 105 // Any time two $$ appear, it is collapsed down to a single $, 106 // but the next character is passed through un-interpreted (even if it 107 // is a brace). 108 109 if (ch == '$') 110 { 111 // This is effectively a symbol, meaning that the input string 112 // will not equal the output string. 113 114 anySymbols = true; 115 116 if (blockLength > 0) 117 result.append(buffer, blockStart, blockLength); 118 119 result.append(ch); 120 121 i++; 122 blockStart = i; 123 blockLength = 0; 124 state = STATE_START; 125 126 continue; 127 } 128 129 // The '$' was just what it was, not the start of a ${} expression 130 // block, so include it as part of the static text block. 131 132 blockLength++; 133 134 state = STATE_START; 135 continue; 136 137 case STATE_COLLECT_SYMBOL_NAME: 138 139 if (ch != '}') 140 { 141 if (ch == '{') 142 braceDepth++; 143 144 i++; 145 symbolLength++; 146 continue; 147 } 148 149 braceDepth--; 150 151 if (braceDepth > 0) 152 { 153 i++; 154 symbolLength++; 155 continue; 156 } 157 158 // Hit the closing brace of a symbol. 159 160 // Degenerate case: the string "${}". 161 162 if (symbolLength == 0) 163 blockLength += 3; 164 165 // Append anything up to the start of the sequence (this is static 166 // text between symbol references). 167 168 if (blockLength > 0) 169 result.append(buffer, blockStart, blockLength); 170 171 if (symbolLength > 0) 172 { 173 String variableName = text.substring(symbolStart, symbolStart 174 + symbolLength); 175 176 result.append(expandSymbol(variableName, location)); 177 178 anySymbols = true; 179 } 180 181 i++; 182 blockStart = i; 183 blockLength = 0; 184 185 // And drop into state start 186 187 state = STATE_START; 188 189 continue; 190 } 191 192 } 193 194 // If get this far without seeing any variables, then just pass 195 // the input back. 196 197 if (!anySymbols) 198 return text; 199 200 // OK, to handle the end. Couple of degenerate cases where 201 // a ${...} was incomplete, so we adust the block length. 202 203 if (state == STATE_DOLLAR) 204 blockLength++; 205 206 if (state == STATE_COLLECT_SYMBOL_NAME) 207 blockLength += symbolLength + 2; 208 209 if (blockLength > 0) 210 result.append(buffer, blockStart, blockLength); 211 212 return result.toString(); 213 } 214 215 private String expandSymbol(String name, Location location) 216 { 217 String value = _source.valueForSymbol(name); 218 219 if (value != null) 220 return value; 221 222 _errorHandler.error(LOG, ImplMessages.noSuchSymbol(name), location, null); 223 224 return "${" + name + "}"; 225 } 226 227 }