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 }