001    // Copyright 2006, 2007 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.tapestry5.ioc.internal.services;
016    
017    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
018    import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newLinkedList;
019    import org.apache.tapestry5.ioc.services.SymbolProvider;
020    import org.apache.tapestry5.ioc.services.SymbolSource;
021    
022    import java.util.LinkedList;
023    import java.util.List;
024    import java.util.Map;
025    
026    public class SymbolSourceImpl implements SymbolSource
027    {
028        private final List<SymbolProvider> providers;
029    
030        /**
031         * Cache of symbol name to fully expanded symbol value.
032         */
033        private final Map<String, String> cache = CollectionFactory.newConcurrentMap();
034    
035        /**
036         * Contains execution data needed when performing an expansion (largely, to check for endless recursion).
037         */
038        private class SymbolExpansion
039        {
040            private final LinkedList<String> expandingSymbols = newLinkedList();
041    
042            String expandSymbols(String input)
043            {
044                StringBuilder builder = null;
045    
046                int startx = 0;
047    
048                while (true)
049                {
050                    int symbolx = input.indexOf("${", startx);
051    
052                    // Special case: if the string contains no symbols then return it as is.
053    
054                    if (startx == 0 && symbolx < 0) return input;
055    
056                    // The string has at least one symbol, so its OK to create the StringBuilder
057    
058                    if (builder == null) builder = new StringBuilder();
059    
060                    // No more symbols found, so add in the rest of the string.
061    
062                    if (symbolx < 0)
063                    {
064                        builder.append(input.substring(startx));
065                        break;
066                    }
067    
068                    builder.append(input.substring(startx, symbolx));
069    
070                    int endx = input.indexOf("}", symbolx);
071    
072                    if (endx < 0)
073                    {
074                        String message = expandingSymbols.isEmpty() ? ServiceMessages
075                                .missingSymbolCloseBrace(input) : ServiceMessages
076                                .missingSymbolCloseBraceInPath(input, path());
077    
078                        throw new RuntimeException(message);
079                    }
080    
081                    String symbolName = input.substring(symbolx + 2, endx);
082    
083                    builder.append(valueForSymbol(symbolName));
084    
085                    // Restart the search after the '}'
086    
087                    startx = endx + 1;
088                }
089    
090                return builder.toString();
091            }
092    
093            String valueForSymbol(String symbolName)
094            {
095                String value = cache.get(symbolName);
096    
097                if (value == null)
098                {
099                    value = expandSymbol(symbolName);
100    
101                    cache.put(symbolName, value);
102                }
103    
104                return value;
105            }
106    
107            String expandSymbol(String symbolName)
108            {
109                if (expandingSymbols.contains(symbolName))
110                {
111                    expandingSymbols.add(symbolName);
112                    throw new RuntimeException(ServiceMessages.recursiveSymbol(
113                            symbolName,
114                            pathFrom(symbolName)));
115                }
116    
117                expandingSymbols.addLast(symbolName);
118    
119                String value = null;
120    
121                for (SymbolProvider provider : providers)
122                {
123                    value = provider.valueForSymbol(symbolName);
124    
125                    if (value != null) break;
126                }
127    
128                if (value == null)
129                {
130    
131                    String message = expandingSymbols.size() == 1 ? ServiceMessages
132                            .symbolUndefined(symbolName) : ServiceMessages.symbolUndefinedInPath(
133                            symbolName,
134                            path());
135    
136                    throw new RuntimeException(message);
137                }
138    
139                // The value may have symbols that need expansion.
140    
141                String result = expandSymbols(value);
142    
143                // And we're done expanding this symbol
144    
145                expandingSymbols.removeLast();
146    
147                return result;
148    
149            }
150    
151            String path()
152            {
153                StringBuilder builder = new StringBuilder();
154    
155                boolean first = true;
156    
157                for (String symbolName : expandingSymbols)
158                {
159                    if (!first) builder.append(" --> ");
160    
161                    builder.append(symbolName);
162    
163                    first = false;
164                }
165    
166                return builder.toString();
167            }
168    
169            String pathFrom(String startSymbolName)
170            {
171                StringBuilder builder = new StringBuilder();
172    
173                boolean first = true;
174                boolean match = false;
175    
176                for (String symbolName : expandingSymbols)
177                {
178                    if (!match)
179                    {
180                        if (symbolName.equals(startSymbolName))
181                            match = true;
182                        else
183                            continue;
184                    }
185    
186                    if (!first) builder.append(" --> ");
187    
188                    builder.append(symbolName);
189    
190                    first = false;
191                }
192    
193                return builder.toString();
194            }
195        }
196    
197        public SymbolSourceImpl(final List<SymbolProvider> providers)
198        {
199            this.providers = providers;
200        }
201    
202        public String expandSymbols(String input)
203        {
204            return new SymbolExpansion().expandSymbols(input);
205        }
206    
207        public String valueForSymbol(String symbolName)
208        {
209            String value = cache.get(symbolName);
210    
211            // If already in the cache, then return it. Otherwise, let the SE find the value and
212            // update the cache.
213    
214            return value != null ? value : new SymbolExpansion().valueForSymbol(symbolName);
215        }
216    
217    }