// Copyright 2006, 2007 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.anjlab.eclipse.tapestry5.internal; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.anjlab.eclipse.tapestry5.TapestrySymbol; /** * Contains execution data needed when performing an expansion (largely, to check for endless recursion). */ public class SymbolExpansion { private final Map<String, String> cache = new HashMap<String, String>(); private final LinkedList<String> expandingSymbols = new LinkedList<String>(); private final Map<String, List<TapestrySymbol>> symbols; public SymbolExpansion(Map<String, List<TapestrySymbol>> symbols) { this.symbols = symbols; } public String expandSymbols(String input) { StringBuilder builder = null; int startx = 0; while (true) { int symbolx = input.indexOf("${", startx); // Special case: if the string contains no symbols then return it as is. if (startx == 0 && symbolx < 0) return input; // The string has at least one symbol, so its OK to create the StringBuilder if (builder == null) builder = new StringBuilder(); // No more symbols found, so add in the rest of the string. if (symbolx < 0) { builder.append(input.substring(startx)); break; } builder.append(input.substring(startx, symbolx)); int endx = input.indexOf("}", symbolx); if (endx < 0) { String message = expandingSymbols.isEmpty() ? String.format("Input string '%s' is missing a symbol closing brace.", input) : String.format("Input string '%s' is missing a symbol closing brace (in %s).", input, path()); throw new RuntimeException(message); } String symbolName = input.substring(symbolx + 2, endx); builder.append(valueForSymbol(symbolName)); // Restart the search after the '}' startx = endx + 1; } return builder.toString(); } String valueForSymbol(String symbolName) { String value = cache.get(symbolName); if (value == null) { value = expandSymbol(symbolName); cache.put(symbolName, value); } return value; } String expandSymbol(String symbolName) { if (expandingSymbols.contains(symbolName)) { expandingSymbols.add(symbolName); throw new RuntimeException(String.format("Symbol '%s' is defined in terms of itself (%s).", symbolName, pathFrom(symbolName))); } expandingSymbols.addLast(symbolName); String value = null; List<TapestrySymbol> values = symbols.get(symbolName); if (values != null) { for (TapestrySymbol symbol : values) { if (!symbol.isOverridden()) { value = symbol.getValue(); // XXX Throw exception if value matches "<.*>" (i.e. evaluation failed) break; } } } if (value == null) { String message = expandingSymbols.size() == 1 ? String.format("Symbol '%s' is not defined.", symbolName) : String.format("Symbol '%s' is not defined (in %s).", symbolName, path()); throw new RuntimeException(message); } // The value may have symbols that need expansion. String result = expandSymbols(value); // And we're done expanding this symbol expandingSymbols.removeLast(); return result; } String path() { StringBuilder builder = new StringBuilder(); boolean first = true; for (String symbolName : expandingSymbols) { if (!first) builder.append(" --> "); builder.append(symbolName); first = false; } return builder.toString(); } String pathFrom(String startSymbolName) { StringBuilder builder = new StringBuilder(); boolean first = true; boolean match = false; for (String symbolName : expandingSymbols) { if (!match) { if (symbolName.equals(startSymbolName)) match = true; else continue; } if (!first) builder.append(" --> "); builder.append(symbolName); first = false; } return builder.toString(); } }