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 }