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.commons.util.CollectionFactory; 018import org.apache.tapestry5.ioc.services.SymbolProvider; 019import org.apache.tapestry5.ioc.services.SymbolSource; 020 021import static org.apache.tapestry5.commons.util.CollectionFactory.newLinkedList; 022 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Map; 026 027public class SymbolSourceImpl implements SymbolSource 028{ 029 private final List<SymbolProvider> providers; 030 031 /** 032 * Cache of symbol name to fully expanded symbol value. 033 */ 034 private final Map<String, String> cache = CollectionFactory.newConcurrentMap(); 035 036 /** 037 * Contains execution data needed when performing an expansion (largely, to check for endless recursion). 038 */ 039 private class SymbolExpansion 040 { 041 private final LinkedList<String> expandingSymbols = newLinkedList(); 042 043 String expandSymbols(String input) 044 { 045 StringBuilder builder = null; 046 047 int startx = 0; 048 049 while (true) 050 { 051 int symbolx = input.indexOf("${", startx); 052 053 // Special case: if the string contains no symbols then return it as is. 054 055 if (startx == 0 && symbolx < 0) return input; 056 057 // The string has at least one symbol, so its OK to create the StringBuilder 058 059 if (builder == null) builder = new StringBuilder(); 060 061 // No more symbols found, so add in the rest of the string. 062 063 if (symbolx < 0) 064 { 065 builder.append(input.substring(startx)); 066 break; 067 } 068 069 builder.append(input.substring(startx, symbolx)); 070 071 int endx = input.indexOf("}", symbolx); 072 073 if (endx < 0) 074 { 075 String message = expandingSymbols.isEmpty() 076 ? String.format("Input string '%s' is missing a symbol closing brace.", input) 077 : String.format("Input string '%s' is missing a symbol closing brace (in %s).", input, path()); 078 079 throw new RuntimeException(message); 080 } 081 082 String symbolName = input.substring(symbolx + 2, endx); 083 084 builder.append(valueForSymbol(symbolName)); 085 086 // Restart the search after the '}' 087 088 startx = endx + 1; 089 } 090 091 return builder.toString(); 092 } 093 094 String valueForSymbol(String symbolName) 095 { 096 String value = cache.get(symbolName); 097 098 if (value == null) 099 { 100 value = expandSymbol(symbolName); 101 102 cache.put(symbolName, value); 103 } 104 105 return value; 106 } 107 108 String expandSymbol(String symbolName) 109 { 110 if (expandingSymbols.contains(symbolName)) 111 { 112 expandingSymbols.add(symbolName); 113 throw new RuntimeException(String.format("Symbol '%s' is defined in terms of itself (%s).", 114 symbolName, 115 pathFrom(symbolName))); 116 } 117 118 expandingSymbols.addLast(symbolName); 119 120 String value = null; 121 122 for (SymbolProvider provider : providers) 123 { 124 value = provider.valueForSymbol(symbolName); 125 126 if (value != null) break; 127 } 128 129 if (value == null) 130 { 131 132 String message = expandingSymbols.size() == 1 133 ? String.format("Symbol '%s' is not defined.", symbolName) 134 : String.format("Symbol '%s' is not defined (in %s).", symbolName, 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 @Override 203 public String expandSymbols(String input) 204 { 205 return new SymbolExpansion().expandSymbols(input); 206 } 207 208 @Override 209 public String valueForSymbol(String symbolName) 210 { 211 String value = cache.get(symbolName); 212 213 // If already in the cache, then return it. Otherwise, let the SE find the value and 214 // update the cache. 215 216 return value != null ? value : new SymbolExpansion().valueForSymbol(symbolName); 217 } 218 219}