blob: c34467a1505219953bd69d5bbf2708ca542995c7 [file] [log] [blame]
/*
* Copyright 1999-2004 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 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: MapStackResolver.java,v 1.3 2004/03/05 13:02:41 bdelacretaz Exp $
* @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();
//
// }
}