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
015package org.apache.tapestry5.ioc.internal.services;
016
017import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
018import static org.apache.tapestry5.ioc.internal.util.CollectionFactory.newLinkedList;
019import org.apache.tapestry5.ioc.services.SymbolProvider;
020import org.apache.tapestry5.ioc.services.SymbolSource;
021
022import java.util.LinkedList;
023import java.util.List;
024import java.util.Map;
025
026public 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}