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    }