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: * FormulaFunction.java
027: * ------------
028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
029: */package org.jfree.report.function;
030:
031: import org.jfree.formula.Formula;
032: import org.jfree.formula.FormulaContext;
033: import org.jfree.report.event.ReportEvent;
034: import org.jfree.util.Configuration;
035: import org.jfree.util.Log;
036:
037: /**
038: * The formula function is a stateful version of the FormulaExpression and is used to evaluate a LibFormula/OpenFormula
039: * expression. This function can be used if a stateful evaluation is needed or if the formula value should be
040: * initialized to a certain value before the real evaluation starts.
041: *
042: * @author Thomas Morgner
043: */
044: public final class FormulaFunction extends AbstractFunction {
045: /**
046: * A cached version of the compiled formula.
047: */
048: private transient Formula compiledFormula;
049: /**
050: * The formula namespace as defined by OpenFormula.
051: */
052: private String formulaNamespace;
053: /**
054: * The formula itself.
055: */
056: private String formulaExpression;
057: /**
058: * The formula as specified by the user. This is the formula and the namespace.
059: */
060: private String formula;
061:
062: /**
063: * The formula namespace of the initial formula as defined by OpenFormula.
064: */
065: private String initialNamespace;
066: /**
067: * The initial formula itself.
068: */
069: private String initialExpression;
070: /**
071: * The initial formula as specified by the user. This is the formula and the namespace.
072: */
073: private String initial;
074: /**
075: * A flag indicating whether the initial-formula has been evaluated.
076: */
077: private boolean initialized;
078: /**
079: * A flag indicating that the formula cannot be parsed.
080: */
081: private boolean formulaError;
082:
083: /**
084: * Default Constructor.
085: */
086: public FormulaFunction() {
087: }
088:
089: /**
090: * Returns the defined formula context from the report processing context.
091: *
092: * @return the formula context.
093: */
094: private FormulaContext getFormulaContext() {
095: final ProcessingContext globalContext = getRuntime()
096: .getProcessingContext();
097: return globalContext.getFormulaContext();
098: }
099:
100: /**
101: * Returns the initial formula (incuding the optional namespace) as defined by the OpenFormula standard.
102: *
103: * @return the formula as text.
104: */
105: public String getInitial() {
106: return initial;
107: }
108:
109: /**
110: * Returns the initial formula expression. The initial formula is used at the first call only.
111: *
112: * @return the initial formula expression.
113: */
114: public String getInitialExpression() {
115: return initialExpression;
116: }
117:
118: /**
119: * Returns the formula namespace if the initial formula. If the formula specified by the user starts with "=", then
120: * the namespace "report" is assumed.
121: *
122: * @return the namespace of the formula.
123: */
124: public String getInitialNamespace() {
125: return initialNamespace;
126: }
127:
128: /**
129: * Defines the initial formula (incuding the optional namespace) as defined by the OpenFormula standard. The initial
130: * formula is used at the first call only.
131: *
132: * @param initial the initial formula as text.
133: */
134: public void setInitial(final String initial) {
135: this .initial = initial;
136: if (initial == null) {
137: initialNamespace = null;
138: initialExpression = null;
139: } else {
140: final int separator = initial.indexOf(':');
141: if (separator <= 0 || ((separator + 1) == initial.length())) {
142: if (formula.length() > 0 && formula.charAt(0) == '=') {
143: initialNamespace = "report";
144: initialExpression = initial.substring(1);
145: } else {
146: // error: invalid formula.
147: initialNamespace = null;
148: initialExpression = null;
149: }
150: } else {
151: initialNamespace = initial.substring(0, separator);
152: initialExpression = initial.substring(separator + 1);
153: }
154: }
155: }
156:
157: /**
158: * Resets the function state.
159: *
160: * @param event the report event.
161: */
162: public void reportInitialized(final ReportEvent event) {
163: initialized = false;
164: }
165:
166: /**
167: * Returns the formula (incuding the optional namespace) as defined by the OpenFormula standard.
168: *
169: * @return the formula as text.
170: */
171: public String getFormula() {
172: return formula;
173: }
174:
175: /**
176: * Returns the formula namespace. If the formula specified by the user starts with "=", then the namespace "report" is
177: * assumed.
178: *
179: * @return the namespace of the formula.
180: */
181: public String getFormulaNamespace() {
182: return formulaNamespace;
183: }
184:
185: /**
186: * Returns the formula expression.
187: *
188: * @return the formula expression.
189: */
190: public String getFormulaExpression() {
191: return formulaExpression;
192: }
193:
194: /**
195: * Defines the formula (incuding the optional namespace) as defined by the OpenFormula standard.
196: *
197: * @param formula the formula as text.
198: */
199: public void setFormula(final String formula) {
200: this .formula = formula;
201: if (formula == null) {
202: formulaNamespace = null;
203: formulaExpression = null;
204: } else {
205: final int separator = formula.indexOf(':');
206: if (separator <= 0 || ((separator + 1) == formula.length())) {
207: if (formula.length() > 0 && formula.charAt(0) == '=') {
208: formulaNamespace = "report";
209: formulaExpression = formula.substring(1);
210: } else {
211: // error: invalid formula.
212: formulaNamespace = null;
213: formulaExpression = null;
214: }
215: } else {
216: formulaNamespace = formula.substring(0, separator);
217: formulaExpression = formula.substring(separator + 1);
218: }
219: }
220: this .compiledFormula = null;
221: this .formulaError = false;
222: }
223:
224: /**
225: * Computes the value of the formula by evaluating the initial formula against the current data-row.
226: *
227: * @return the computed value or null, if an error occured.
228: */
229: private Object computeInitialValue() {
230: try {
231: if (initial != null) {
232: final Formula initFormula = new Formula(
233: initialExpression);
234: final ReportFormulaContext context = new ReportFormulaContext(
235: getFormulaContext(), getDataRow(), getRuntime()
236: .getExportDescriptor());
237: try {
238: initFormula.initialize(context);
239: return initFormula.evaluate();
240: } finally {
241: context.setDataRow(null);
242: }
243: }
244:
245: // if the code above did not trigger, compute a regular thing ..
246: return computeRegularValue();
247: } catch (Exception e) {
248: if (Log.isDebugEnabled()) {
249: final Configuration config = getReportConfiguration();
250: if ("true"
251: .equals(config
252: .getConfigProperty("org.jfree.report.function.LogFormulaFailureCause"))) {
253: Log.debug("Failed to compute the initial value ["
254: + formulaExpression + ']', e);
255: } else {
256: Log.debug("Failed to compute the initial value ["
257: + formulaExpression + ']');
258: }
259: }
260: return null;
261: }
262: }
263:
264: /**
265: * Computes the value of the formula by evaluating the formula against the current data-row.
266: *
267: * @return the computed value or null, if an error occured.
268: */
269: private Object computeRegularValue() {
270: if (formulaError) {
271: return null;
272: }
273:
274: try {
275: if (compiledFormula == null) {
276: compiledFormula = new Formula(formulaExpression);
277: }
278:
279: final ReportFormulaContext context = new ReportFormulaContext(
280: getFormulaContext(), getDataRow(), getRuntime()
281: .getExportDescriptor());
282: try {
283: compiledFormula.initialize(context);
284: return compiledFormula.evaluate();
285: } finally {
286: context.setDataRow(null);
287: }
288: } catch (Exception e) {
289: formulaError = true;
290: if (Log.isDebugEnabled()) {
291: final Configuration config = getReportConfiguration();
292: if ("true"
293: .equals(config
294: .getConfigProperty("org.jfree.report.function.LogFormulaFailureCause"))) {
295: Log.debug("Failed to compute the regular value ["
296: + formulaExpression + ']', e);
297: } else {
298: Log.debug("Failed to compute the regular value ["
299: + formulaExpression + ']');
300: }
301: }
302: return null;
303: }
304: }
305:
306: /**
307: * Return the computed value of the formula. The first call will return the initial-value instead.
308: *
309: * @return the value of the function.
310: */
311: public Object getValue() {
312: try {
313: if (initialized == false) {
314: initialized = true;
315: return computeInitialValue();
316: }
317: return computeRegularValue();
318: } catch (Exception e) {
319: return null;
320: }
321: }
322:
323: /**
324: * Clones the expression, expression should be reinitialized after the cloning. <P> Expression maintain no state,
325: * cloning is done at the beginning of the report processing to disconnect the used expression from any other object
326: * space.
327: *
328: * @return A clone of this expression.
329: * @throws CloneNotSupportedException this should never happen.
330: */
331: public Object clone() throws CloneNotSupportedException {
332: final FormulaFunction o = (FormulaFunction) super .clone();
333: if (compiledFormula != null) {
334: o.compiledFormula = (Formula) compiledFormula.clone();
335: }
336: return o;
337: }
338: }
|