001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.components.treeprocessor;
018:
019: import java.util.ArrayList;
020: import java.util.Collections;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.avalon.framework.parameters.Parameters;
027: import org.apache.cocoon.sitemap.PatternException;
028:
029: /**
030: * Utility class for handling {...} pattern substitutions from a List of Maps.
031: *
032: * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a>
033: * @version CVS $Id: MapStackResolver.java 433543 2006-08-22 06:22:54Z crossley $
034: * @deprecated use {@link org.apache.cocoon.components.treeprocessor.variables.VariableResolverFactory}
035: */
036:
037: public abstract class MapStackResolver {
038:
039: public static final Map EMPTY_MAP = Collections
040: .unmodifiableMap(new java.util.HashMap(0));
041:
042: /**
043: * Resolve all {...} patterns using the values given in the list of maps.
044: */
045: public abstract String resolve(List mapStack)
046: throws PatternException;
047:
048: /**
049: * Does an expression need resolving (i.e. contain {...} patterns) ?
050: */
051: public static boolean needsResolve(String expression) {
052: if (expression == null || expression.length() == 0) {
053: return false;
054: }
055:
056: // Is the first char a '{' ?
057: if (expression.charAt(0) == '{') {
058: return true;
059: }
060:
061: if (expression.length() < 2) {
062: return false;
063: }
064:
065: // Is there any unescaped '{' ?
066: int pos = 1;
067: while ((pos = expression.indexOf('{', pos)) != -1) {
068: // Found a '{' : is it escaped ?
069: if (expression.charAt(pos - 1) != '\\') {
070: // No : need to resolve
071: return true;
072: }
073: pos++;
074: }
075: // Nothing found...
076: return false;
077: }
078:
079: /**
080: * Unescape an expression that doesn't need to be resolved, but may contain
081: * escaped '{' characters.
082: *
083: * @param expression the expression to unescape.
084: * @return the unescaped result, or <code>expression</code> if unescaping isn't necessary.
085: */
086: public static String unescape(String expression) {
087: // Does it need escaping ?
088: if (expression == null || expression.indexOf("\\{") == -1) {
089: return expression;
090: }
091:
092: StringBuffer buf = new StringBuffer();
093: for (int i = 0; i < expression.length(); i++) {
094: char ch = expression.charAt(i);
095: if (ch != '\\' || i >= (expression.length() - 1)
096: || expression.charAt(i + 1) != '{') {
097: buf.append(ch);
098: }
099: }
100:
101: return buf.toString();
102: }
103:
104: /**
105: * Get a resolver for a given expression. Chooses the most efficient implementation
106: * depending on <code>expression</code>.
107: */
108: public static MapStackResolver getResolver(String expression)
109: throws PatternException {
110: if (needsResolve(expression)) {
111: // return new RealResolver(expression);
112: return new CompiledResolver(expression);
113: } else {
114: return new NullResolver(expression);
115: }
116: }
117:
118: /**
119: * Build a <code>Parameters</code> object from a Map of named <code>ListOfMapResolver</code>s and
120: * a list of Maps used for resolution.
121: *
122: * @return a fully resolved <code>Parameters</code>.
123: */
124: public static Parameters buildParameters(Map expressions,
125: List mapStack) throws PatternException {
126: if (expressions == null || expressions.size() == 0) {
127: return Parameters.EMPTY_PARAMETERS;
128: }
129:
130: Parameters result = new Parameters();
131:
132: Iterator iter = expressions.entrySet().iterator();
133: while (iter.hasNext()) {
134: Map.Entry entry = (Map.Entry) iter.next();
135: result.setParameter(((MapStackResolver) entry.getKey())
136: .resolve(mapStack), ((MapStackResolver) entry
137: .getValue()).resolve(mapStack));
138: }
139:
140: return result;
141: }
142:
143: /**
144: * Resolve all values of a <code>Map</code> from a Map of named <code>ListOfMapResolver</code>s and
145: * a list of Maps used for resolution.
146: *
147: * @return a fully resolved <code>Map</code>.
148: */
149: public static Map resolveMap(Map expressions, List mapStack)
150: throws PatternException {
151: int size;
152: if (expressions == null || (size = expressions.size()) == 0) {
153: return EMPTY_MAP;
154: }
155:
156: Map result = new HashMap(size);
157:
158: Iterator iter = expressions.entrySet().iterator();
159: while (iter.hasNext()) {
160: Map.Entry entry = (Map.Entry) iter.next();
161: result.put(((MapStackResolver) entry.getKey())
162: .resolve(mapStack), ((MapStackResolver) entry
163: .getValue()).resolve(mapStack));
164: }
165:
166: return result;
167: }
168:
169: //-------------------------------------------------------------------------
170: /**
171: * No-op resolver for expressions that don't need to be resolved.
172: */
173: private static class NullResolver extends MapStackResolver {
174: private String originalExpr = null;
175: private String expression = null;
176:
177: public NullResolver(String expression) {
178: if (expression != null) {
179: this .originalExpr = expression;
180: this .expression = unescape(expression);
181: }
182: }
183:
184: public String toString() {
185: return this .originalExpr;
186: }
187:
188: public final String resolve(List mapStack) {
189: return this .expression;
190: }
191: }
192:
193: //-------------------------------------------------------------------------
194:
195: /**
196: * Compiled form for faster substitution
197: */
198: private static class CompiledResolver extends MapStackResolver {
199: private String originalExpr;
200:
201: private String[] strings;
202: private int[] levels;
203:
204: public CompiledResolver(String expression)
205: throws PatternException {
206: this .originalExpr = expression;
207: compile(expression);
208: }
209:
210: public String toString() {
211: return this .originalExpr;
212: }
213:
214: private void compile(String expr) throws PatternException {
215: // We're sure here that expr *contains* some substitutions
216:
217: List stringList = new ArrayList();
218: List levelList = new ArrayList();
219:
220: int length = expr.length();
221: int prev = 0; // position after last closing brace
222:
223: comp: while (prev < length) {
224: // find next unescaped '{'
225: int pos = prev;
226: while (pos < length
227: && (pos = expr.indexOf('{', pos)) != -1
228: && (pos != 0 && expr.charAt(pos - 1) == '\\')) {
229: pos++;
230: }
231:
232: if (pos >= length || pos == -1) {
233: // no more braces
234: if (prev < length) {
235: stringList.add(unescape(expr.substring(prev)));
236: levelList.add(new Integer(-1));
237: }
238: break comp;
239: }
240:
241: // Pass closing brace
242: pos++;
243:
244: // Add litteral strings between closing and next opening brace
245: if (prev < pos - 1) {
246: stringList.add(unescape(expr.substring(prev,
247: pos - 1)));
248: levelList.add(new Integer(-1));
249: }
250:
251: // Determine subst level
252: int level = 1; // Start at 1 since it will be substracted from list.size()
253: while (expr.startsWith("../", pos)) {
254: level++;
255: pos += "../".length();
256: }
257:
258: int end = expr.indexOf('}', pos);
259: if (end == -1) {
260: throw new PatternException("Unmatched '{' in "
261: + expr);
262: }
263:
264: stringList.add(expr.substring(pos, end));
265: levelList.add(new Integer(level));
266:
267: prev = end + 1;
268: }
269:
270: this .strings = new String[stringList.size()];
271: this .levels = new int[stringList.size()];
272: for (int i = 0; i < strings.length; i++) {
273: this .strings[i] = (String) stringList.get(i);
274: this .levels[i] = ((Integer) levelList.get(i))
275: .intValue();
276: }
277: }
278:
279: public final String resolve(List mapStack)
280: throws PatternException {
281: StringBuffer result = new StringBuffer();
282: int stackSize = mapStack.size();
283:
284: for (int i = 0; i < this .strings.length; i++) {
285: int level = this .levels[i];
286: if (level == -1) {
287: result.append(this .strings[i]);
288:
289: } else {
290: if (level > stackSize) {
291: throw new PatternException(
292: "Error while evaluating '"
293: + this .originalExpr
294: + "' : not so many levels");
295: }
296:
297: Object value = ((Map) mapStack.get(stackSize
298: - level)).get(this .strings[i]);
299: if (value != null) {
300: result.append(value);
301: }
302: }
303: }
304:
305: return result.toString();
306: }
307:
308: // public void dump() {
309: // System.out.println(this.originalExpr + " compiled in :");
310: // for (int i = 0; i < this.strings.length; i++) {
311: // System.out.print("[" + this.levels[i] + ":'" + this.strings[i] + "'] ");
312: // }
313: // System.out.println();
314: // System.out.println();
315: // }
316: }
317:
318: // public static void main(String [] args) throws Exception {
319: //
320: // new CompiledResolver("&{../../blah}").dump();
321: // new CompiledResolver("{t1}tt{t2}x").dump();
322: // new CompiledResolver("\\{t1}tt{t2}xx").dump();
323: // new CompiledResolver("{t1}tt\\{t2}xx").dump();
324: // new CompiledResolver("{t1}tt{t2}xx").dump();
325: // new CompiledResolver("xx{../t1}{../../../t2}zz").dump();
326: // new CompiledResolver("xx{../t1}\\{../../../t2}zz").dump();
327: //
328: // }
329: }
|