1 /**
2 * =========================================================
3 * Pentaho-Reporting-Classic : a free Java reporting library
4 * =========================================================
5 *
6 * Project Info: http://reporting.pentaho.org/
7 *
8 * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
9 *
10 * This library is free software; you can redistribute it and/or modify it under the terms
11 * of the GNU Lesser General Public License as published by the Free Software Foundation;
12 * either version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
15 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 * See the GNU Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License along with this
19 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
20 * Boston, MA 02111-1307, USA.
21 *
22 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
23 * in the United States and other countries.]
24 *
25 * ------------
26 * BSHExpression.java
27 * ------------
28 * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
29 */
30
31 package org.jfree.report.modules.misc.beanshell;
32
33 import java.io.BufferedInputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.io.ObjectInputStream;
38 import java.io.Reader;
39
40 import bsh.EvalError;
41 import bsh.Interpreter;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.jfree.report.function.AbstractExpression;
45 import org.jfree.report.function.Expression;
46 import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
47
48 /**
49 * An expression that uses the BeanShell scripting framework to perform a scripted
50 * calculation. The expression itself is contained in a function called
51 * <p/>
52 * <code>Object getValue()</code>
53 * <p/>
54 * and this function is defined in the <code>expression</code> property. You have to
55 * overwrite the function <code>getValue()</code> to begin and to end your expression, but
56 * you are free to add your own functions to the script.
57 * <p/>
58 * By default, base Java core and extension packages are imported for you. They are: <ul>
59 * <li><code>java.lang<code> <li><code>java.io</code> <li><code>java.util</code>
60 * <li><code>java.net</code> <li><code>java.awt</code> <li><code>java.awt.event</code>
61 * <li><code>javax.swing</code> <li><code>javax.swing.event</code> </ul>
62 * <p/>
63 * An example in the XML format: (from report1.xml)
64 * <p/>
65 * <pre><expression name="expression" class="org.jfree.report.modules.misc.beanshell.BSHExpression">
66 * <properties>
67 * <property name="expression">
68 * // you may import packages and classes or use the fully qualified name of the class
69 * import org.jfree.report.*;
70 * <p/>
71 * String userdefinedFunction (String parameter, Date date)
72 * {
73 * return parameter + " - the current date is " + date);
74 * }
75 * <p/>
76 * // use simple java code to perform the expression. You may use all classes
77 * // available in your classpath as if you write "real" java code in your favourite
78 * // IDE.
79 * // See the www.beanshell.org site for more information ...
80 * //
81 * // A return value of type "Object" is alway implied ...
82 * getValue ()
83 * {
84 * return userdefinedFunction ("Hello World :) ", new Date());
85 * }
86 * </property>
87 * </properties>
88 * </expression></pre>
89 *
90 * @author Thomas Morgner
91 */
92 public class BSHExpression extends AbstractExpression
93 {
94 private static Log logger = LogFactory.getLog (BSHExpression.class);
95 /**
96 * The headerfile with the default initialisations.
97 */
98 public static final String BSHHEADERFILE =
99 "org/jfree/report/modules/misc/beanshell/BSHExpressionHeader.txt"; //$NON-NLS-1$
100
101 /**
102 * The beanshell-interpreter used to evaluate the expression.
103 */
104 private transient Interpreter interpreter;
105 private transient boolean invalid;
106
107 private String expression;
108
109 /**
110 * default constructor, create a new BeanShellExpression.
111 */
112 public BSHExpression()
113 {
114 }
115
116 /**
117 * This method tries to create a new and fully initialized BeanShell interpreter.
118 *
119 * @return the interpreter or null, if there was no way to create the interpreter.
120 */
121 protected Interpreter createInterpreter()
122 {
123 try
124 {
125 final Interpreter interpreter = new Interpreter();
126 initializeInterpreter(interpreter);
127 return interpreter;
128 }
129 catch (Exception e)
130 {
131 BSHExpression.logger.error("Unable to initialize the expression", e); //$NON-NLS-1$
132 return null;
133 }
134 }
135
136 /**
137 * Initializes the bean-shell interpreter by executing the code in
138 * the BSHExpressionHeader.txt file.
139 *
140 * @param interpreter the interpreter that should be initialized.
141 * @throws EvalError if an BeanShell-Error occured.
142 * @throws IOException if the beanshell file could not be read.
143 */
144 protected void initializeInterpreter(final Interpreter interpreter)
145 throws EvalError, IOException
146 {
147 final InputStream in = ObjectUtilities.getResourceRelativeAsStream
148 ("BSHExpressionHeader.txt", BSHExpression.class); //$NON-NLS-1$
149 // read the header, creates a skeleton
150 final Reader r = new InputStreamReader(new BufferedInputStream(in));
151 try
152 {
153 interpreter.eval(r);
154 }
155 finally
156 {
157 r.close();
158 }
159
160 // now add the userdefined expression
161 // the expression is given in form of a function with the signature of:
162 //
163 // Object getValue ()
164 //
165 if (getExpression() != null)
166 {
167 interpreter.eval(expression);
168 }
169 }
170
171 /**
172 * Evaluates the defined expression. If an exception or an evaluation error occures, the
173 * evaluation returns null and the error is logged. The current datarow and a copy of
174 * the expressions properties are set to script-internal variables. Changes to the
175 * properties will not alter the expressions original properties and will be lost when
176 * the evaluation is finished.
177 * <p/>
178 * Expressions do not maintain a state and no assumptions about the order of evaluation
179 * can be made.
180 *
181 * @return the evaluated value or null.
182 */
183 public Object getValue()
184 {
185 if (invalid)
186 {
187 return null;
188 }
189 if (interpreter == null)
190 {
191 interpreter = createInterpreter();
192 if (interpreter == null)
193 {
194 invalid = true;
195 return null;
196 }
197 }
198 try
199 {
200 interpreter.set("dataRow", getDataRow()); //$NON-NLS-1$
201 return interpreter.eval("getValue ();"); //$NON-NLS-1$
202 }
203 catch (Exception e)
204 {
205 BSHExpression.logger.warn("Evaluation error: " + //$NON-NLS-1$
206 e.getClass() + " - " + e.getMessage(), e); //$NON-NLS-1$
207 return null;
208 }
209 }
210
211 /**
212 * Return a new instance of this expression. The copy is initialized and uses the same
213 * parameters as the original, but does not share any objects.
214 *
215 * @return a copy of this function.
216 */
217 public Expression getInstance()
218 {
219 final BSHExpression ex = (BSHExpression) super.getInstance();
220 ex.interpreter = null;
221 return ex;
222 }
223
224 /**
225 * Serialisation support. The transient child elements were not saved.
226 *
227 * @param in the input stream.
228 * @throws IOException if there is an I/O error.
229 * @throws ClassNotFoundException if a serialized class is not defined on this system.
230 */
231 private void readObject(final ObjectInputStream in)
232 throws IOException, ClassNotFoundException
233 {
234 in.defaultReadObject();
235 }
236
237 /**
238 * Sets the beanshell script as string.
239 *
240 * @return the script.
241 */
242 public String getExpression()
243 {
244 return expression;
245 }
246
247 /**
248 * Sets the beanshell script that should be executed. The script should
249 * define a getValue() method which returns a single object.
250 *
251 * @param expression the script.
252 */
253 public void setExpression(final String expression)
254 {
255 this.expression = expression;
256 this.invalid = false;
257 this.interpreter = null;
258 }
259 }