/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.cocoon.components.treeprocessor; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.avalon.framework.parameters.Parameters; import org.apache.cocoon.sitemap.PatternException; /** * Utility class for handling {...} pattern substitutions from a List of Maps. * * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a> * @version CVS $Id$ * @deprecated use {@link org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory} */ public abstract class MapStackResolver { public static final Map EMPTY_MAP = Collections.unmodifiableMap(new java.util.HashMap(0)); /** * Resolve all {...} patterns using the values given in the list of maps. */ public abstract String resolve(List mapStack) throws PatternException; /** * Does an expression need resolving (i.e. contain {...} patterns) ? */ public static boolean needsResolve(String expression) { if (expression == null || expression.length() == 0) { return false; } // Is the first char a '{' ? if (expression.charAt(0) == '{') { return true; } if (expression.length() < 2) { return false; } // Is there any unescaped '{' ? int pos = 1; while ( (pos = expression.indexOf('{', pos)) != -1) { // Found a '{' : is it escaped ? if (expression.charAt(pos - 1) != '\\') { // No : need to resolve return true; } pos++; } // Nothing found... return false; } /** * Unescape an expression that doesn't need to be resolved, but may contain * escaped '{' characters. * * @param expression the expression to unescape. * @return the unescaped result, or <code>expression</code> if unescaping isn't necessary. */ public static String unescape(String expression) { // Does it need escaping ? if (expression == null || expression.indexOf("\\{") == -1) { return expression; } StringBuffer buf = new StringBuffer(); for (int i = 0; i < expression.length(); i++) { char ch = expression.charAt(i); if (ch != '\\' || i >= (expression.length() - 1) || expression.charAt(i+1) != '{') { buf.append(ch); } } return buf.toString(); } /** * Get a resolver for a given expression. Chooses the most efficient implementation * depending on <code>expression</code>. */ public static MapStackResolver getResolver(String expression) throws PatternException { if (needsResolve(expression)) { // return new RealResolver(expression); return new CompiledResolver(expression); } else { return new NullResolver(expression); } } /** * Build a <code>Parameters</code> object from a Map of named <code>ListOfMapResolver</code>s and * a list of Maps used for resolution. * * @return a fully resolved <code>Parameters</code>. */ public static Parameters buildParameters(Map expressions, List mapStack) throws PatternException { if (expressions == null || expressions.size() == 0) { return Parameters.EMPTY_PARAMETERS; } Parameters result = new Parameters(); Iterator iter = expressions.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); result.setParameter( ((MapStackResolver)entry.getKey()).resolve(mapStack), ((MapStackResolver)entry.getValue()).resolve(mapStack) ); } return result; } /** * Resolve all values of a <code>Map</code> from a Map of named <code>ListOfMapResolver</code>s and * a list of Maps used for resolution. * * @return a fully resolved <code>Map</code>. */ public static Map resolveMap(Map expressions, List mapStack) throws PatternException { int size; if (expressions == null || (size = expressions.size()) == 0) { return EMPTY_MAP; } Map result = new HashMap(size); Iterator iter = expressions.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = (Map.Entry)iter.next(); result.put( ((MapStackResolver)entry.getKey()).resolve(mapStack), ((MapStackResolver)entry.getValue()).resolve(mapStack) ); } return result; } //------------------------------------------------------------------------- /** * No-op resolver for expressions that don't need to be resolved. */ private static class NullResolver extends MapStackResolver { private String originalExpr = null; private String expression = null; public NullResolver(String expression) { if (expression != null) { this.originalExpr = expression; this.expression = unescape(expression); } } public String toString() { return this.originalExpr; } public final String resolve(List mapStack) { return this.expression; } } //------------------------------------------------------------------------- /** * Compiled form for faster substitution */ private static class CompiledResolver extends MapStackResolver { private String originalExpr; private String[] strings; private int[] levels; public CompiledResolver(String expression) throws PatternException { this.originalExpr = expression; compile(expression); } public String toString() { return this.originalExpr; } private void compile(String expr) throws PatternException { // We're sure here that expr *contains* some substitutions List stringList = new ArrayList(); List levelList = new ArrayList(); int length = expr.length(); int prev = 0; // position after last closing brace comp : while(prev < length) { // find next unescaped '{' int pos = prev; while(pos < length && (pos = expr.indexOf('{', pos)) != -1 && (pos != 0 && expr.charAt(pos - 1) == '\\')) { pos++; } if (pos >= length || pos == -1) { // no more braces if (prev < length) { stringList.add(unescape(expr.substring(prev))); levelList.add(new Integer(-1)); } break comp; } // Pass closing brace pos++; // Add litteral strings between closing and next opening brace if (prev < pos-1) { stringList.add(unescape(expr.substring(prev, pos - 1))); levelList.add(new Integer(-1)); } // Determine subst level int level = 1; // Start at 1 since it will be substracted from list.size() while(expr.startsWith("../", pos)) { level++; pos += "../".length(); } int end = expr.indexOf('}', pos); if (end == -1) { throw new PatternException("Unmatched '{' in " + expr); } stringList.add(expr.substring(pos, end)); levelList.add(new Integer(level)); prev = end + 1; } this.strings = new String[stringList.size()]; this.levels = new int[stringList.size()]; for (int i = 0; i < strings.length; i++) { this.strings[i] = (String)stringList.get(i); this.levels[i] = ((Integer)levelList.get(i)).intValue(); } } public final String resolve(List mapStack) throws PatternException { StringBuffer result = new StringBuffer(); int stackSize = mapStack.size(); for (int i = 0; i < this.strings.length; i++) { int level = this.levels[i]; if (level == -1) { result.append(this.strings[i]); } else { if (level > stackSize) { throw new PatternException("Error while evaluating '" + this.originalExpr + "' : not so many levels"); } Object value = ((Map)mapStack.get(stackSize - level)).get(this.strings[i]); if (value != null) { result.append(value); } } } return result.toString(); } // public void dump() { // System.out.println(this.originalExpr + " compiled in :"); // for (int i = 0; i < this.strings.length; i++) { // System.out.print("[" + this.levels[i] + ":'" + this.strings[i] + "'] "); // } // System.out.println(); // System.out.println(); // } } // public static void main(String [] args) throws Exception { // // new CompiledResolver("&{../../blah}").dump(); // new CompiledResolver("{t1}tt{t2}x").dump(); // new CompiledResolver("\\{t1}tt{t2}xx").dump(); // new CompiledResolver("{t1}tt\\{t2}xx").dump(); // new CompiledResolver("{t1}tt{t2}xx").dump(); // new CompiledResolver("xx{../t1}{../../../t2}zz").dump(); // new CompiledResolver("xx{../t1}\\{../../../t2}zz").dump(); // // } }