001: /**
002: * ===========================================
003: * JFreeReport : a free Java reporting library
004: * ===========================================
005: *
006: * Project Info: http://reporting.pentaho.org/
007: *
008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
009: *
010: * This library is free software; you can redistribute it and/or modify it under the terms
011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
012: * either version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: * See the GNU Lesser General Public License for more details.
017: *
018: * You should have received a copy of the GNU Lesser General Public License along with this
019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
020: * Boston, MA 02111-1307, USA.
021: *
022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
023: * in the United States and other countries.]
024: *
025: * ------------
026: * BSHExpression.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.modules.misc.beanshell;
030:
031: import java.io.BufferedInputStream;
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.io.InputStreamReader;
035: import java.io.ObjectInputStream;
036: import java.io.Reader;
037:
038: import bsh.EvalError;
039: import bsh.Interpreter;
040: import org.jfree.report.function.AbstractExpression;
041: import org.jfree.report.function.Expression;
042: import org.jfree.util.Log;
043: import org.jfree.util.ObjectUtilities;
044:
045: /**
046: * An expression that uses the BeanShell scripting framework to perform a scripted
047: * calculation. The expression itself is contained in a function called
048: * <p/>
049: * <code>Object getValue()</code>
050: * <p/>
051: * and this function is defined in the <code>expression</code> property. You have to
052: * overwrite the function <code>getValue()</code> to begin and to end your expression, but
053: * you are free to add your own functions to the script.
054: * <p/>
055: * By default, base Java core and extension packages are imported for you. They are: <ul>
056: * <li><code>java.lang<code> <li><code>java.io</code> <li><code>java.util</code>
057: * <li><code>java.net</code> <li><code>java.awt</code> <li><code>java.awt.event</code>
058: * <li><code>javax.swing</code> <li><code>javax.swing.event</code> </ul>
059: * <p/>
060: * An example in the XML format: (from report1.xml)
061: * <p/>
062: * <pre><expression name="expression" class="org.jfree.report.modules.misc.beanshell.BSHExpression">
063: * <properties>
064: * <property name="expression">
065: * // you may import packages and classes or use the fully qualified name of the class
066: * import org.jfree.report.*;
067: * <p/>
068: * String userdefinedFunction (String parameter, Date date)
069: * {
070: * return parameter + " - the current date is " + date);
071: * }
072: * <p/>
073: * // use simple java code to perform the expression. You may use all classes
074: * // available in your classpath as if you write "real" java code in your favourite
075: * // IDE.
076: * // See the www.beanshell.org site for more information ...
077: * //
078: * // A return value of type "Object" is alway implied ...
079: * getValue ()
080: * {
081: * return userdefinedFunction ("Hello World :) ", new Date());
082: * }
083: * </property>
084: * </properties>
085: * </expression></pre>
086: *
087: * @author Thomas Morgner
088: */
089: public class BSHExpression extends AbstractExpression {
090: /**
091: * The headerfile with the default initialisations.
092: */
093: public static final String BSHHEADERFILE = "org/jfree/report/modules/misc/beanshell/BSHExpressionHeader.txt"; //$NON-NLS-1$
094:
095: /**
096: * The beanshell-interpreter used to evaluate the expression.
097: */
098: private transient Interpreter interpreter;
099: private transient boolean invalid;
100:
101: private String expression;
102:
103: /**
104: * default constructor, create a new BeanShellExpression.
105: */
106: public BSHExpression() {
107: }
108:
109: /**
110: * This method tries to create a new and fully initialized BeanShell interpreter.
111: *
112: * @return the interpreter or null, if there was no way to create the interpreter.
113: */
114: protected Interpreter createInterpreter() {
115: try {
116: final Interpreter interpreter = new Interpreter();
117: initializeInterpreter(interpreter);
118: return interpreter;
119: } catch (Exception e) {
120: Log.error("Unable to initialize the expression", e); //$NON-NLS-1$
121: return null;
122: }
123: }
124:
125: /**
126: * Initializes the bean-shell interpreter by executing the code in
127: * the BSHExpressionHeader.txt file.
128: *
129: * @param interpreter the interpreter that should be initialized.
130: * @throws EvalError if an BeanShell-Error occured.
131: * @throws IOException if the beanshell file could not be read.
132: */
133: protected void initializeInterpreter(final Interpreter interpreter)
134: throws EvalError, IOException {
135: final InputStream in = ObjectUtilities
136: .getResourceRelativeAsStream(
137: "BSHExpressionHeader.txt", BSHExpression.class); //$NON-NLS-1$
138: // read the header, creates a skeleton
139: final Reader r = new InputStreamReader(new BufferedInputStream(
140: in));
141: try {
142: interpreter.eval(r);
143: } finally {
144: r.close();
145: }
146:
147: // now add the userdefined expression
148: // the expression is given in form of a function with the signature of:
149: //
150: // Object getValue ()
151: //
152: if (getExpression() != null) {
153: interpreter.eval(expression);
154: }
155: }
156:
157: /**
158: * Evaluates the defined expression. If an exception or an evaluation error occures, the
159: * evaluation returns null and the error is logged. The current datarow and a copy of
160: * the expressions properties are set to script-internal variables. Changes to the
161: * properties will not alter the expressions original properties and will be lost when
162: * the evaluation is finished.
163: * <p/>
164: * Expressions do not maintain a state and no assumptions about the order of evaluation
165: * can be made.
166: *
167: * @return the evaluated value or null.
168: */
169: public Object getValue() {
170: if (invalid) {
171: return null;
172: }
173: if (interpreter == null) {
174: interpreter = createInterpreter();
175: if (interpreter == null) {
176: invalid = true;
177: return null;
178: }
179: }
180: try {
181: interpreter.set("dataRow", getDataRow()); //$NON-NLS-1$
182: return interpreter.eval("getValue ();"); //$NON-NLS-1$
183: } catch (Exception e) {
184: Log.warn(new Log.SimpleMessage("Evaluation error: ", //$NON-NLS-1$
185: e.getClass(), " - ", e.getMessage()), e); //$NON-NLS-1$
186: return null;
187: }
188: }
189:
190: /**
191: * Return a new instance of this expression. The copy is initialized and uses the same
192: * parameters as the original, but does not share any objects.
193: *
194: * @return a copy of this function.
195: */
196: public Expression getInstance() {
197: final BSHExpression ex = (BSHExpression) super .getInstance();
198: ex.interpreter = null;
199: return ex;
200: }
201:
202: /**
203: * Serialisation support. The transient child elements were not saved.
204: *
205: * @param in the input stream.
206: * @throws IOException if there is an I/O error.
207: * @throws ClassNotFoundException if a serialized class is not defined on this system.
208: */
209: private void readObject(final ObjectInputStream in)
210: throws IOException, ClassNotFoundException {
211: in.defaultReadObject();
212: }
213:
214: /**
215: * Sets the beanshell script as string.
216: *
217: * @return the script.
218: */
219: public String getExpression() {
220: return expression;
221: }
222:
223: /**
224: * Sets the beanshell script that should be executed. The script should
225: * define a getValue() method which returns a single object.
226: *
227: * @param expression the script.
228: */
229: public void setExpression(final String expression) {
230: this .expression = expression;
231: this .invalid = false;
232: this.interpreter = null;
233: }
234: }
|