001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 1999 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution, if
020: * any, must include the following acknowlegement:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowlegement may appear in the software itself,
024: * if and wherever such third-party acknowlegements normally appear.
025: *
026: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
027: * Foundation" must not be used to endorse or promote products derived
028: * from this software without prior written permission. For written
029: * permission, please contact apache@apache.org.
030: *
031: * 5. Products derived from this software may not be called "Apache"
032: * nor may "Apache" appear in their names without prior written
033: * permission of the Apache Group.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of the Apache Software Foundation. For more
051: * information on the Apache Software Foundation, please see
052: * <http://www.apache.org/>.
053: *
054: */
055:
056: package org.jbpm.jpdl.el.impl;
057:
058: import java.io.Reader;
059: import java.io.StringReader;
060: import java.text.MessageFormat;
061: import java.util.Collections;
062: import java.util.HashMap;
063: import java.util.Map;
064:
065: import org.jbpm.jpdl.el.ELException;
066: import org.jbpm.jpdl.el.ExpressionEvaluator;
067: import org.jbpm.jpdl.el.FunctionMapper;
068: import org.jbpm.jpdl.el.VariableResolver;
069: import org.jbpm.jpdl.el.parser.ELParser;
070: import org.jbpm.jpdl.el.parser.ParseException;
071: import org.jbpm.jpdl.el.parser.ELToken;
072: import org.jbpm.jpdl.el.parser.ELTokenMgrError;
073:
074: /**
075: *
076: * <p>This is the main class for evaluating expression Strings. An
077: * expression String is a String that may contain expressions of the
078: * form ${...}. Multiple expressions may appear in the same
079: * expression String. In such a case, the expression String's value
080: * is computed by concatenating the String values of those evaluated
081: * expressions and any intervening non-expression text, then
082: * converting the resulting String to the expected type using the
083: * PropertyEditor mechanism.
084: *
085: * <p>In the special case where the expression String is a single
086: * expression, the value of the expression String is determined by
087: * evaluating the expression, without any intervening conversion to a
088: * String.
089: *
090: * <p>The evaluator maintains a cache mapping expression Strings to
091: * their parsed results. For expression Strings containing no
092: * expression elements, it maintains a cache mapping
093: * ExpectedType/ExpressionString to parsed value, so that static
094: * expression Strings won't have to go through a conversion step every
095: * time they are used. All instances of the evaluator share the same
096: * cache. The cache may be bypassed by setting a flag on the
097: * evaluator's constructor.
098: *
099: * <p>The evaluator must be passed a VariableResolver in its
100: * constructor. The VariableResolver is used to resolve variable
101: * names encountered in expressions, and can also be used to implement
102: * "implicit objects" that are always present in the namespace.
103: * Different applications will have different policies for variable
104: * lookups and implicit objects - these differences can be
105: * encapsulated in the VariableResolver passed to the evaluator's
106: * constructor.
107: *
108: * <p>Most VariableResolvers will need to perform their resolution
109: * against some context. For example, a JSP environment needs a
110: * PageContext to resolve variables. The evaluate() method takes a
111: * generic Object context which is eventually passed to the
112: * VariableResolver - the VariableResolver is responsible for casting
113: * the context to the proper type.
114: *
115: * <p>Once an evaluator instance has been constructed, it may be used
116: * multiple times, and may be used by multiple simultaneous Threads.
117: * In other words, an evaluator instance is well-suited for use as a
118: * singleton.
119: *
120: * @author Nathan Abramson - Art Technology Group
121: * @author Shawn Bayern
122: * @version $Change: 181177 $$DateTime: 2001/06/26 08:45:09 $$Author$
123: **/
124:
125: public class ExpressionEvaluatorImpl extends ExpressionEvaluator {
126: //-------------------------------------
127: // Properties
128: //-------------------------------------
129:
130: //-------------------------------------
131: // Member variables
132: //-------------------------------------
133:
134: /** The mapping from expression String to its parsed form (String,
135: Expression, or ExpressionString) **/
136: static Map sCachedExpressionStrings = Collections
137: .synchronizedMap(new HashMap());
138:
139: /** The mapping from ExpectedType to Maps mapping literal String to
140: parsed value **/
141: static Map sCachedExpectedTypes = new HashMap();
142:
143: /** The static Logger **/
144: static Logger sLogger = new Logger(System.out);
145:
146: /** Flag if the cache should be bypassed **/
147: boolean mBypassCache;
148:
149: //-------------------------------------
150: /**
151: *
152: * Constructor
153: **/
154: public ExpressionEvaluatorImpl() {
155: }
156:
157: /**
158: *
159: * Constructor
160: *
161: * @param pBypassCache flag indicating if the cache should be
162: * bypassed
163: **/
164: public ExpressionEvaluatorImpl(boolean pBypassCache) {
165: mBypassCache = pBypassCache;
166: }
167:
168: //-------------------------------------
169: /**
170: *
171: * Evaluates the given expression String
172: *
173: * @param pExpressionString The expression to be evaluated.
174: * @param pExpectedType The expected type of the result of the evaluation
175: * @param pResolver A VariableResolver instance that can be used at
176: * runtime to resolve the name of implicit objects into Objects.
177: * @param functions A FunctionMapper to resolve functions found in
178: * the expression. It can be null, in which case no functions
179: * are supported for this invocation.
180: * @return the expression String evaluated to the given expected
181: * type
182: **/
183: public Object evaluate(String pExpressionString,
184: Class pExpectedType, VariableResolver pResolver,
185: FunctionMapper functions) throws ELException {
186: return evaluate(pExpressionString, pExpectedType, pResolver,
187: functions, sLogger);
188: }
189:
190: /**
191: *
192: * Prepare an expression for later evaluation. This method should perform
193: * syntactic validation of the expression; if in doing so it detects
194: * errors, it should raise an ELParseException.
195: *
196: * @param expression The expression to be evaluated.
197: * @param expectedType The expected type of the result of the evaluation
198: * @param fMapper A FunctionMapper to resolve functions found in
199: * the expression. It can be null, in which case no functions
200: * are supported for this invocation. The ExpressionEvaluator
201: * must not hold on to the FunctionMapper reference after
202: * returning from <code>parseExpression()</code>. The
203: * <code>Expression</code> object returned must invoke the same
204: * functions regardless of whether the mappings in the
205: * provided <code>FunctionMapper</code> instance change between
206: * calling <code>ExpressionEvaluator.parseExpression()</code>
207: * and <code>Expression.evaluate()</code>.
208: * @return The Expression object encapsulating the arguments.
209: *
210: * @exception ELException Thrown if parsing errors were found.
211: **/
212: public org.jbpm.jpdl.el.Expression parseExpression(
213: String expression, Class expectedType,
214: FunctionMapper fMapper) throws ELException {
215: // Validate and then create an Expression object.
216: parseExpressionString(expression);
217:
218: // Create an Expression object that knows how to evaluate this.
219: return new JSTLExpression(this , expression, expectedType,
220: fMapper);
221: }
222:
223: //-------------------------------------
224: /**
225: *
226: * Evaluates the given expression string
227: **/
228: Object evaluate(String pExpressionString, Class pExpectedType,
229: VariableResolver pResolver, FunctionMapper functions,
230: Logger pLogger) throws ELException {
231: // Check for null expression strings
232: if (pExpressionString == null) {
233: throw new ELException(Constants.NULL_EXPRESSION_STRING);
234: }
235:
236: // Get the parsed version of the expression string
237: Object parsedValue = parseExpressionString(pExpressionString);
238:
239: // Evaluate differently based on the parsed type
240: if (parsedValue instanceof String) {
241: // Convert the String, and cache the conversion
242: String strValue = (String) parsedValue;
243: return convertStaticValueToExpectedType(strValue,
244: pExpectedType, pLogger);
245: }
246:
247: else if (parsedValue instanceof Expression) {
248: // Evaluate the expression and convert
249: Object value = ((Expression) parsedValue).evaluate(
250: pResolver, functions, pLogger);
251: return convertToExpectedType(value, pExpectedType, pLogger);
252: }
253:
254: else if (parsedValue instanceof ExpressionString) {
255: // Evaluate the expression/string list and convert
256: String strValue = ((ExpressionString) parsedValue)
257: .evaluate(pResolver, functions, pLogger);
258: return convertToExpectedType(strValue, pExpectedType,
259: pLogger);
260: }
261:
262: else {
263: // This should never be reached
264: return null;
265: }
266: }
267:
268: //-------------------------------------
269: /**
270: *
271: * Gets the parsed form of the given expression string. If the
272: * parsed form is cached (and caching is not bypassed), return the
273: * cached form, otherwise parse and cache the value. Returns either
274: * a String, Expression, or ExpressionString.
275: **/
276: public Object parseExpressionString(String pExpressionString)
277: throws ELException {
278: // See if it's an empty String
279: if (pExpressionString.length() == 0) {
280: return "";
281: }
282:
283: // See if it's in the cache
284: Object ret = mBypassCache ? null : sCachedExpressionStrings
285: .get(pExpressionString);
286:
287: if (ret == null) {
288: // Parse the expression
289: Reader r = new StringReader(pExpressionString);
290: ELParser parser = new ELParser(r);
291: try {
292: ret = parser.ExpressionString();
293: sCachedExpressionStrings.put(pExpressionString, ret);
294: } catch (ParseException exc) {
295: throw new ELException(formatParseException(
296: pExpressionString, exc));
297: } catch (ELTokenMgrError exc) {
298: // Note - this should never be reached, since the parser is
299: // constructed to tokenize any input (illegal inputs get
300: // parsed to <BADLY_ESCAPED_STRING_LITERAL> or
301: // <ILLEGAL_CHARACTER>
302: throw new ELException(exc.getMessage());
303: }
304: }
305: return ret;
306: }
307:
308: //-------------------------------------
309: /**
310: *
311: * Converts the given value to the specified expected type.
312: **/
313: Object convertToExpectedType(Object pValue, Class pExpectedType,
314: Logger pLogger) throws ELException {
315: return Coercions.coerce(pValue, pExpectedType, pLogger);
316: }
317:
318: //-------------------------------------
319: /**
320: *
321: * Converts the given String, specified as a static expression
322: * string, to the given expected type. The conversion is cached.
323: **/
324: Object convertStaticValueToExpectedType(String pValue,
325: Class pExpectedType, Logger pLogger) throws ELException {
326: // See if the value is already of the expected type
327: if (pExpectedType == String.class
328: || pExpectedType == Object.class) {
329: return pValue;
330: }
331:
332: // Find the cached value
333: Map valueByString = getOrCreateExpectedTypeMap(pExpectedType);
334: if (!mBypassCache && valueByString.containsKey(pValue)) {
335: return valueByString.get(pValue);
336: } else {
337: // Convert from a String
338: Object ret = Coercions.coerce(pValue, pExpectedType,
339: pLogger);
340: valueByString.put(pValue, ret);
341: return ret;
342: }
343: }
344:
345: //-------------------------------------
346: /**
347: *
348: * Creates or returns the Map that maps string literals to parsed
349: * values for the specified expected type.
350: **/
351: static Map getOrCreateExpectedTypeMap(Class pExpectedType) {
352: synchronized (sCachedExpectedTypes) {
353: Map ret = (Map) sCachedExpectedTypes.get(pExpectedType);
354: if (ret == null) {
355: ret = Collections.synchronizedMap(new HashMap());
356: sCachedExpectedTypes.put(pExpectedType, ret);
357: }
358: return ret;
359: }
360: }
361:
362: //-------------------------------------
363: // Formatting ParseException
364: //-------------------------------------
365: /**
366: *
367: * Formats a ParseException into an error message suitable for
368: * displaying on a web page
369: **/
370: static String formatParseException(String pExpressionString,
371: ParseException pExc) {
372: // Generate the String of expected tokens
373: StringBuffer expectedBuf = new StringBuffer();
374: int maxSize = 0;
375: boolean printedOne = false;
376:
377: if (pExc.expectedTokenSequences == null)
378: return pExc.toString();
379:
380: for (int i = 0; i < pExc.expectedTokenSequences.length; i++) {
381: if (maxSize < pExc.expectedTokenSequences[i].length) {
382: maxSize = pExc.expectedTokenSequences[i].length;
383: }
384: for (int j = 0; j < pExc.expectedTokenSequences[i].length; j++) {
385: if (printedOne) {
386: expectedBuf.append(", ");
387: }
388: expectedBuf
389: .append(pExc.tokenImage[pExc.expectedTokenSequences[i][j]]);
390: printedOne = true;
391: }
392: }
393: String expected = expectedBuf.toString();
394:
395: // Generate the String of encountered tokens
396: StringBuffer encounteredBuf = new StringBuffer();
397: ELToken tok = pExc.currentToken.next;
398: for (int i = 0; i < maxSize; i++) {
399: if (i != 0)
400: encounteredBuf.append(" ");
401: if (tok.kind == 0) {
402: encounteredBuf.append(pExc.tokenImage[0]);
403: break;
404: }
405: encounteredBuf.append(addEscapes(tok.image));
406: tok = tok.next;
407: }
408: String encountered = encounteredBuf.toString();
409:
410: // Format the error message
411: return MessageFormat.format(Constants.PARSE_EXCEPTION,
412: new Object[] { expected, encountered, });
413: }
414:
415: //-------------------------------------
416: /**
417: *
418: * Used to convert raw characters to their escaped version when
419: * these raw version cannot be used as part of an ASCII string
420: * literal.
421: **/
422: static String addEscapes(String str) {
423: StringBuffer retval = new StringBuffer();
424: char ch;
425: for (int i = 0; i < str.length(); i++) {
426: switch (str.charAt(i)) {
427: case 0:
428: continue;
429: case '\b':
430: retval.append("\\b");
431: continue;
432: case '\t':
433: retval.append("\\t");
434: continue;
435: case '\n':
436: retval.append("\\n");
437: continue;
438: case '\f':
439: retval.append("\\f");
440: continue;
441: case '\r':
442: retval.append("\\r");
443: continue;
444: default:
445: if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) {
446: String s = "0000" + Integer.toString(ch, 16);
447: retval.append("\\u"
448: + s.substring(s.length() - 4, s.length()));
449: } else {
450: retval.append(ch);
451: }
452: continue;
453: }
454: }
455: return retval.toString();
456: }
457:
458: //-------------------------------------
459: // Testing methods
460: //-------------------------------------
461: /**
462: *
463: * Parses the given expression string, then converts it back to a
464: * String in its canonical form. This is used to test parsing.
465: **/
466: public String parseAndRender(String pExpressionString)
467: throws ELException {
468: Object val = parseExpressionString(pExpressionString);
469: if (val instanceof String) {
470: return (String) val;
471: } else if (val instanceof Expression) {
472: return "${" + ((Expression) val).getExpressionString()
473: + "}";
474: } else if (val instanceof ExpressionString) {
475: return ((ExpressionString) val).getExpressionString();
476: } else {
477: return "";
478: }
479: }
480:
481: /**
482: * An object that encapsulates an expression to be evaluated by
483: * the JSTL evaluator.
484: */
485: private class JSTLExpression extends org.jbpm.jpdl.el.Expression {
486: private ExpressionEvaluatorImpl evaluator;
487: private String expression;
488: private Class expectedType;
489: private FunctionMapper fMapper;
490:
491: public JSTLExpression(ExpressionEvaluatorImpl evaluator,
492: String expression, Class expectedType,
493: FunctionMapper fMapper) {
494: this .evaluator = evaluator;
495: this .expression = expression;
496: this .expectedType = expectedType;
497: this .fMapper = fMapper;
498: }
499:
500: public Object evaluate(VariableResolver vResolver)
501: throws ELException {
502: return evaluator.evaluate(this .expression,
503: this .expectedType, vResolver, this .fMapper);
504: }
505: }
506:
507: //-------------------------------------
508:
509: }
|