/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.forms.formmodel.algorithms; import java.math.BigDecimal; import java.util.Collection; import java.util.Iterator; import org.apache.avalon.framework.CascadingError; import org.apache.cocoon.forms.datatype.Datatype; import org.apache.cocoon.forms.formmodel.CannotYetResolveWarning; import org.apache.cocoon.forms.formmodel.ExpressionContextImpl; import org.apache.cocoon.forms.formmodel.Form; import org.apache.cocoon.forms.formmodel.Widget; import org.apache.cocoon.forms.util.WidgetFinder; import org.outerj.expression.Expression; import org.outerj.expression.ExpressionException; /** * An xreporter {@link org.outerj.expression.Expression} based algorithm that repeats the formula on a * set of fields. * * <p> * The specified formula will be applied iterating on the specified widgets. The final result will be the result of the * last iteration. From inside the formula you can access this two extra variables : * <dl> * <dt>formulaResult</dt> * <dd>The result of the previous iteration, or the result of the initial result if this is the first iteration.</dd> * <dt>formulaCurrent</dt> * <dd>The value of the current trigger widget.</dd> * </dl> * </p> * <p> * The initial result is evaluated before starting the iteration, and its value is used as a formulaResult for the * first iteration. * </p> * <p> * It's possible to define nearly every cyclic arithmetic operation with this algorithm, for example : * <dl> * <dt>Sum</dt> * <dd>initial-result="0" formula="formulaResult + formulaCurrent"</dd> * <dt>Multiplication</dt> * <dd>initial-result="1" formula="formulaResult * formulaCurrent"</dd> * </dl> * </p> * <p> * More over, thru the use of advanced xreporter syntax it's possible to quickly implement also complex * algorithms: * <ul> * <li>Count all items with a price higher than 100 : eval="formulaResult + If(price > 100, 1, 0)" * (read : the result is the previous result plus one if price is over 100, 0 if price is less than 100)</li> * <li>Obtain a sum of all movements, wether they are positive or negative amount movements : * eval="formulaResult + Abs(amount)"</li> * <li>Count how many slots are empty in the 10 items box you are using for packaging : * eval="formulaResult + Reminder(items, 10)" * </ul> * </p> * <p> * Note: please take care that xreporter expressions are not that accurate when it comes to decimals. The default * divide operator rounds the result, see http://issues.cocoondev.org/browse/XRP-115. Also consider that the * available set of functions can be expanded implementing and using new ones. Please see * <a href="http://outerthought.net/wqm/xreporter/en/expressions.html"> * http://outerthought.net/wqm/xreporter/en/expressions.html</a> for an overview of xreportes expressions and * {@link org.apache.cocoon.forms.expression.IsNullFunction} or * {@link org.apache.cocoon.forms.expression.StringFunction} * for examples of custom xreporter functions. * </p> * @version $Id$ */ public class RepeatedFormula extends SimpleFormula { private Expression initialResult = null; private String repeatOn = null; public Object calculate(Form form, Widget parent, Datatype datatype) { try { Object result = null; if (initialResult != null) { result = initialResult.evaluate(new ExpressionContextImpl(parent, true)); } WidgetFinder finder = new WidgetFinder(parent, this.repeatOn, false); Collection widgets = finder.getWidgets(); for (Iterator iter = widgets.iterator(); iter.hasNext();) { Widget widget = (Widget) iter.next(); ResultExpressionContext ctx = new ResultExpressionContext(widget, result); result = formula.evaluate(ctx); } return result; } catch (CannotYetResolveWarning w) { return null; } catch (ExpressionException e) { throw new CascadingError("Error evaluating calculated field formula", e); } } static class ResultExpressionContext extends ExpressionContextImpl { Object result = null; Widget current = null; public ResultExpressionContext(Widget widget, Object result) { super(widget.getParent(), true); current = widget; this.result = result; } public Object resolveVariable(String name) { if (name.equals("formulaResult")) { return result; } if (name.equals("formulaCurrent")) { Object value = current.getValue(); if (value == null && current.isRequired()) { throw new CannotYetResolveWarning(); } if (value instanceof Long) return new BigDecimal(((Long)value).longValue()); else if (value instanceof Integer) return new BigDecimal(((Integer)value).intValue()); else return value; } return super.resolveVariable(name); } } /** * @return Returns the initialResult. */ public Expression getInitialResult() { return initialResult; } /** * @param initialResult The initialResult to set. */ public void setInitialResult(Expression initialResult) { this.initialResult = initialResult; } /** * @return Returns the iterateOn. */ public String getRepeatOn() { return repeatOn; } /** * @param iterateOn The iterateOn to set. */ public void setRepeatOn(String iterateOn) { this.repeatOn = iterateOn; } }