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