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 }