cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From simo...@apache.org
Subject svn commit: r409526 - in /cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms: expression/ formmodel/ formmodel/algorithms/ util/
Date Fri, 26 May 2006 00:53:06 GMT
Author: simoneg
Date: Thu May 25 17:53:05 2006
New Revision: 409526

URL: http://svn.apache.org/viewvc?rev=409526&view=rev
Log:
Calculated fields

Added:
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/SumFunction.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedField.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithm.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithmBuilder.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinition.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinitionBuilder.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithm.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithmBuilder.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaAlgorithmBuilder.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScript.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScriptBuilder.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormula.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormulaBuilder.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormula.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormulaBuilder.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/util/WidgetFinder.java
Modified:
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/DefaultExpressionManager.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/ExpressionManager.java
    cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/ExpressionContextImpl.java

Modified: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/DefaultExpressionManager.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/DefaultExpressionManager.java?rev=409526&r1=409525&r2=409526&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/DefaultExpressionManager.java (original)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/DefaultExpressionManager.java Thu May 25 17:53:05 2006
@@ -16,12 +16,13 @@
 
 package org.apache.cocoon.forms.expression;
 
+import java.util.List;
+
 import org.apache.avalon.framework.component.Component;
 import org.apache.avalon.framework.configuration.Configurable;
 import org.apache.avalon.framework.configuration.Configuration;
 import org.apache.avalon.framework.configuration.ConfigurationException;
 import org.apache.avalon.framework.thread.ThreadSafe;
-
 import org.outerj.expression.DefaultFunctionFactory;
 import org.outerj.expression.Expression;
 import org.outerj.expression.ExpressionException;
@@ -60,13 +61,19 @@
     }
     
     public Expression parse(String expressionString) throws ParseException, ExpressionException {
-        
         FormulaParser parser = new FormulaParser(new java.io.StringReader(expressionString), factory);
-        parser.sum();
+        parser.parse();
 
         Expression expression = parser.getExpression();
         expression.check();
 
         return expression;
     }
+    
+    public List parseVariables(String expressionString) throws ParseException, ExpressionException {
+        FormulaParser parser = new FormulaParser(new java.io.StringReader(expressionString), factory);
+        parser.parse();
+        return parser.getVariables();
+    }
+    
 }

Modified: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/ExpressionManager.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/ExpressionManager.java?rev=409526&r1=409525&r2=409526&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/ExpressionManager.java (original)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/ExpressionManager.java Thu May 25 17:53:05 2006
@@ -15,6 +15,8 @@
  */
 package org.apache.cocoon.forms.expression;
 
+import java.util.List;
+
 import org.outerj.expression.Expression;
 import org.outerj.expression.ParseException;
 import org.outerj.expression.ExpressionException;
@@ -30,5 +32,21 @@
     
     String ROLE = ExpressionManager.class.getName();
     
+    /**
+     * Parse the given expression.
+     * @param expression The string containing the expression to parse.
+     * @return The Expression object resulting from parse.
+     * @throws ParseException If something goes wrong while parsing.
+     * @throws ExpressionException If the expression has been parsed successfully but is invalid.
+     */
     Expression parse(String expression) throws ParseException, ExpressionException;
+    
+    /**
+     * Parse the given expression to extract variables.
+     * @param expressionString The string containing the expression to parse.
+     * @return A {@link List} of {@link org.outerj.expression.VariableFunction}, one for each variable used in the expression. {@see org.outerj.expression.VariableFunction#getVariableName()}.
+     * @throws ParseException If something goes wrong while parsing.
+     * @throws ExpressionException If the expression has been parsed successfully but is invalid.
+     */
+    List parseVariables(String expressionString) throws ParseException, ExpressionException;
 }

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/SumFunction.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/SumFunction.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/SumFunction.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/expression/SumFunction.java Thu May 25 17:53:05 2006
@@ -0,0 +1,85 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.expression;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.outerj.expression.AbstractExpression;
+import org.outerj.expression.Expression;
+import org.outerj.expression.ExpressionContext;
+import org.outerj.expression.ExpressionException;
+
+/**
+ * Sum function. This function returns the sum of all of its argument, but
+ * it accepts Collections or Iterators as arguments. When it finds such an 
+ * argument it iterates on all it's values, and try to sum them as well. It 
+ * accepts String and any instance of Number.
+ */
+public class SumFunction extends AbstractExpression {
+
+    public Object evaluate(ExpressionContext context) throws ExpressionException {
+        BigDecimal result = new BigDecimal("0");
+        for(int i = 0; i < arguments.size(); i++) {
+            Expression function = (Expression)arguments.get(i);
+            Object ret = function.evaluate(context);
+            if (ret instanceof Collection) {
+                ret = ((Collection)ret).iterator();
+            }
+            if (ret instanceof Iterator) {
+               Iterator iter = (Iterator)ret;
+               while (iter.hasNext()) {
+                   Object p = iter.next();
+                   BigDecimal db = null;
+                   if (p instanceof BigDecimal) {
+                       db =(BigDecimal)p;
+                   } else if (p instanceof Long) {
+                       db = new BigDecimal(((Long)p).longValue());
+                   } else if (p instanceof Integer) {
+                       db = new BigDecimal(((Integer)p).intValue());
+                   } else if (p instanceof Double) {
+                       db = new BigDecimal(((Double)p).doubleValue());
+                   } else if (p instanceof Float) {
+                       db = new BigDecimal(((Float)p).floatValue());;
+                   } else if (p instanceof BigInteger) {
+                       db = new BigDecimal((BigInteger)p);
+                   } else if (p instanceof Number) {
+                       db = new BigDecimal(((Number)p).doubleValue());
+                   } else if (p instanceof String) {
+                       db = new BigDecimal((String)p);
+                   } else {
+                       throw new IllegalArgumentException("Cannot sum an argument of type " + p.getClass().getName());
+                   }
+                   result = result.add(db);
+               }
+            } else {
+                result = result.add((BigDecimal)function.evaluate(context));
+            }
+        }
+        return result;        
+    }
+
+    public Class getResultType() {
+        return BigDecimal.class;
+    }
+
+    public String getDescription() {
+        return "Summatory";
+    }
+
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedField.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedField.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedField.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedField.java Thu May 25 17:53:05 2006
@@ -0,0 +1,200 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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;
+
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.cocoon.forms.datatype.Datatype;
+import org.apache.cocoon.forms.datatype.convertor.ConversionResult;
+import org.apache.cocoon.forms.event.RepeaterEvent;
+import org.apache.cocoon.forms.event.RepeaterListener;
+import org.apache.cocoon.forms.event.ValueChangedEvent;
+import org.apache.cocoon.forms.event.ValueChangedListener;
+import org.apache.cocoon.forms.event.ValueChangedListenerEnabled;
+import org.apache.cocoon.forms.util.WidgetFinder;
+
+import com.ibm.icu.math.BigDecimal;
+
+
+/**
+ * A field which calculates its value.
+ * 
+ * <p>A calculated field is useful to create fields containing a sum, or a percentage, or any other
+ * value derived from other fields in the form.</p>
+ * 
+ * <p>The way the field calculates its value is determined by its 
+ * {@link org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm}.
+ * The algorithm is also responsible for determining which other form widgets will trigger
+ * a value calculation for this field.
+ * </p>
+ * 
+ * @version $Id$
+ */
+public class CalculatedField extends Field {
+
+    private CalculatedFieldDefinition definition = null;
+    private CalculatedFieldAlgorithm algorithm = null;
+        
+    private WidgetFinder finder = null;
+    private RecalculateValueListener mockListener = new RecalculateValueListener();
+    
+    private boolean needRecaulculate = false;
+    private boolean initialized = false;
+    private boolean calculating = false;
+
+    
+    /**
+     * @param definition
+     */
+    protected CalculatedField(CalculatedFieldDefinition definition) {
+        super(definition);
+        
+        this.definition = definition;
+        this.algorithm = definition.getAlgorithm();
+    }
+    
+    public void initialize() {
+        super.initialize();
+        Iterator triggers = this.algorithm.getTriggerWidgets();
+        this.finder = new WidgetFinder(this.getParent(), triggers, true);
+        this.finder.addRepeaterListener(new InstallHandlersListener());
+        installHandlers();
+        
+        this.initialized = true;
+    }
+    
+    /**
+     * Installs handlers on other widgets. This both forces other widget to
+     * submit the form when their values change, and also gives this field
+     * a good optimization on calls to its algorithm. 
+     */
+    protected void installHandlers() {
+        List adds = this.finder.getNewAdditions();
+        for (Iterator iter = adds.iterator(); iter.hasNext();) {
+            Widget widget = (Widget) iter.next();
+            if (widget instanceof ValueChangedListenerEnabled) {
+                ((ValueChangedListenerEnabled)widget).addValueChangedListener(mockListener);
+            }
+        }
+    }
+    
+    protected void readFromRequest(String newEnteredValue) {
+        // Never read a calculated field from request.
+    }
+
+    public Object getValue() {
+        // Need to calculate if the following is true.
+        //  - We are not already calculating (to avoid stack overflow)
+        //  - We need to recaulculate.
+        if (!calculating && needRecaulculate) {
+            calculating = true;
+            try {
+                super.setValue(recalculate());
+            } finally {
+                calculating = false;
+            }
+        }
+        return super.getValue();
+    }
+    
+    /**
+     * Calls the algorithm to perform a recaulculation.
+     * @return The calculated value for this field.
+     */
+    protected Object recalculate() {
+        Object ret = this.algorithm.calculate(this.getForm(), this.getParent(), this.getDatatype());
+        needRecaulculate = false;
+        try {
+            ret = convert(ret, this.getDatatype());
+        } catch (Exception e) {
+            // FIXME : log the conversion error
+        }
+        return ret;
+    }
+    
+    /**
+     * Tries to convert the return value of the algorithm to the right value for this field datatype.
+     * @param ret The return value fo the algorithm.
+     * @param datatype The target datatype.
+     * @return A converted value, or the given ret value if no conversion was possible.
+     */
+    protected Object convert(Object ret, Datatype datatype) throws Exception {
+        // First try object to object conversion
+        Class target = datatype.getTypeClass(); 
+        if (ret instanceof Number) {
+            // Try to convert the number back to what expected
+            Number number = (Number)ret; 
+            if (target.equals(BigDecimal.class)) {
+                return number;
+            } else if (target.equals(Double.class)) {
+                ret = new Double(number.doubleValue());
+            } else if (target.equals(Float.class)) {
+                ret = new Float(number.floatValue());
+            } else if (target.equals(Integer.class)) {
+                ret = new Integer(number.intValue());
+            } else if (target.equals(Long.class)) {
+                ret = new Long(number.longValue());
+            } else if (target.equals(String.class)) {
+                ret = number.toString();
+            }
+            return ret;
+        } else if (ret instanceof String) {
+            if (Number.class.isAssignableFrom(target)) {
+                // Try to build a new number parsing the string.
+                ret = target.getConstructor(new Class[] { String.class }).newInstance(new Object[] { ret });
+            }
+            return ret;
+        }
+        // Finally try to use the convertor
+        ConversionResult result = this.getDatatype().convertFromString(ret.toString(), getForm().getLocale());
+        if (result.isSuccessful()) {
+            ret = result.getResult();
+        }
+        return ret;
+    }
+    
+    
+    /**
+     * This listener is added to trigger fields, so that we know when they have been modified AND they are
+     * automatically submitted.
+     */
+    class RecalculateValueListener implements ValueChangedListener {
+        public void valueChanged(ValueChangedEvent event) {
+            needRecaulculate = true;
+            getValue();
+        }
+    }
+
+    /**
+     * This listener is installed on the WidgetFinder to know when some repeater
+     * involved in our calculations gets modified.
+     */
+    class InstallHandlersListener implements RepeaterListener {
+        public void repeaterModified(RepeaterEvent event) {
+            needRecaulculate = true;
+            installHandlers();
+            getValue();
+        }
+    }
+    
+    /**
+     * @return Returns the algorithm.
+     */
+    public CalculatedFieldAlgorithm getAlgorithm() {
+        return algorithm;
+    }
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithm.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithm.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithm.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithm.java Thu May 25 17:53:05 2006
@@ -0,0 +1,54 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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;
+
+import java.util.Iterator;
+
+import org.apache.cocoon.forms.datatype.Datatype;
+
+/**
+ * Common interface for an algorithm to calculate the value of a 
+ * {@link org.apache.cocoon.forms.formmodel.CalculatedField}.
+ * 
+ * @version $Id$
+ */
+public interface CalculatedFieldAlgorithm {
+
+    /**
+     * Checks wether this algorithm is able to return the given datatype. For example,
+     * an arithmetic algorithm like sum should check that the given datatype is a number.
+     * @param dataType The target datatype.
+     * @return true if this algorithm can return a compatible value, false otherwise.
+     */
+    public boolean isSuitableFor(Datatype dataType);
+    
+    /**
+     * Performs the actual calculation.
+     * @param form The form.
+     * @param parent The parent widget of the {@link CalculatedField} widget (may be the same as form)
+     * @param datatype The target datatype.
+     * @return the calculated value for the {@link CalculatedField}.
+     */
+    public Object calculate(Form form, Widget parent, Datatype datatype);
+    
+    /**
+     * Returns an iterator on trigger widget paths. When the value of a trigger widget changes,
+     * then the {@link CalculatedField} value must be recalculated.
+     * @return An iterator of Strings representing widget paths as interpreted by {@link org.apache.cocoon.forms.util.WidgetFinder}.
+     */
+    public Iterator getTriggerWidgets();
+    
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithmBuilder.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithmBuilder.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithmBuilder.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldAlgorithmBuilder.java Thu May 25 17:53:05 2006
@@ -0,0 +1,30 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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;
+
+import org.w3c.dom.Element;
+
+/**
+ * Common interface for {@link org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm} builders.
+ * @version $Id$
+ */
+public interface CalculatedFieldAlgorithmBuilder {
+
+    static final String ROLE = CalculatedFieldAlgorithmBuilder.class.getName();
+
+    CalculatedFieldAlgorithm build(Element algorithmElement) throws Exception;
+    
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinition.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinition.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinition.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinition.java Thu May 25 17:53:05 2006
@@ -0,0 +1,45 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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;
+
+
+/**
+ * Definition of a {@link CalculatedField}.
+ * @version $Id$
+ */
+public class CalculatedFieldDefinition extends FieldDefinition {
+
+    private CalculatedFieldAlgorithm algorithm = null;
+    
+    public Widget createInstance() {
+        return new CalculatedField(this);
+    }
+
+    /**
+     * @return Returns the algorithm.
+     */
+    public CalculatedFieldAlgorithm getAlgorithm() {
+        return algorithm;
+    }
+    /**
+     * @param algorithm The algorithm to set.
+     */
+    public void setAlgorithm(CalculatedFieldAlgorithm algorithm) {
+        super.checkMutable();        
+        this.algorithm = algorithm;
+    }
+    
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinitionBuilder.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinitionBuilder.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinitionBuilder.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/CalculatedFieldDefinitionBuilder.java Thu May 25 17:53:05 2006
@@ -0,0 +1,76 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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;
+
+import org.apache.avalon.framework.service.ServiceSelector;
+import org.apache.cocoon.forms.FormsConstants;
+import org.apache.cocoon.forms.util.DomHelper;
+import org.w3c.dom.Element;
+
+/**
+ * Builder for {@link CalculatedField}s.
+ * 
+ * <p>A common calculated field definition is as follows :</p>
+ * <p><code>
+ *   &lt;fd:calculatedfield id="id" [state="{invisible|output|disabled|active}"]&gt;
+ *     &lt;fd:datatype base="..."&gt;...&lt;/fd:datatype&gt;
+ *     &lt;fd:label&gt;...&lt;/fd:label&gt;
+ *     &lt;fd:value type="..."&gt;...&lt;/fd:algorithm&gt;
+ *   &lt;/fd:calculatedfield&gt;
+ * </code></p>
+ * 
+ * <p>Since it inherits from {@link org.apache.cocoon.forms.formmodel.Field}, 
+ * other attributes and elements may be specified, like listeners (on-value-changed, on-create etc..) or
+ * selection lists (which could make sense if the algorithm calculates one value between many possibilities).
+ * </p>
+ * 
+ * <p> Note that the default state is active, althought typing in a calculated field is useless. The state invisible 
+ * can be used to create fields which are used as temporary value placeholders in a chain of calculations. 
+ * </p>
+ * 
+ * @version $Id$
+ */
+public class CalculatedFieldDefinitionBuilder extends FieldDefinitionBuilder {
+
+    public WidgetDefinition buildWidgetDefinition(Element widgetElement) throws Exception {
+        CalculatedFieldDefinition definition = new CalculatedFieldDefinition();
+        setupDefinition(widgetElement, definition);
+        definition.makeImmutable();
+        return definition;
+    }
+        
+    
+    protected void setupDefinition(Element widgetElement, CalculatedFieldDefinition definition) throws Exception {
+        super.setupDefinition(widgetElement, definition);
+
+        Element algorithmElement = DomHelper.getChildElement(widgetElement, FormsConstants.DEFINITION_NS, "value");
+        ServiceSelector builderSelector = (ServiceSelector)this.serviceManager.lookup(CalculatedFieldAlgorithmBuilder.ROLE + "Selector");
+        CalculatedFieldAlgorithmBuilder builder = null;
+        try {
+            String algorithmType = algorithmElement.getAttribute("type");
+            if (algorithmType.length() == 0) algorithmType = null;
+            builder = (CalculatedFieldAlgorithmBuilder)builderSelector.select(algorithmType);
+            definition.setAlgorithm(builder.build(algorithmElement));
+        } finally {
+            if (builder != null) {
+                builderSelector.release(builder);
+            }
+            this.serviceManager.release(builderSelector);
+        }
+    }
+    
+    
+}

Modified: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/ExpressionContextImpl.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/ExpressionContextImpl.java?rev=409526&r1=409525&r2=409526&view=diff
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/ExpressionContextImpl.java (original)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/ExpressionContextImpl.java Thu May 25 17:53:05 2006
@@ -15,9 +15,14 @@
  */
 package org.apache.cocoon.forms.formmodel;
 
-import org.outerj.expression.ExpressionContext;
-
 import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.cocoon.forms.util.WidgetFinder;
+import org.outerj.expression.ExpressionContext;
 
 /**
  * Implementation of the ExpressionContext required for the evaluation of
@@ -46,12 +51,15 @@
     /**
      * Variables refer to other widgets.
      *
-     * <p>The current implementation only allows access to sibling widgets.
+     * <p>You can access parent and root widgets (with ../widget and /widget paths
+     * respectively), but mind that in xreporter expressions these variables names
+     * must be placed in brakets to be correctly parsed. For example 
+     * "{../widget} + otherWidget".
      *
-     * <p>In case the value of a widget is null but the widget is required, then a special
-     * exception will be thrown, the {@link CannotYetResolveWarning}. This is because in
-     * that case, you'll probably want to re-evaluate the expression at a later time (since
-     * the widget is required, it will eventually get a value).
+     * <p>In case the value of a widget is null but the widget is required or calculated, then 
+     * a special exception will be thrown, the {@link CannotYetResolveWarning}. This is 
+     * because in that case, you'll probably want to re-evaluate the expression at a later time 
+     * (since the widget is required, it will eventually get a value).
      *
      * <p>In case the value of the widget is null but the field is not required, then simply
      * null is returned. (TODO: a function IsNull() will provided in the expression library
@@ -60,37 +68,58 @@
      *
      * <p>If the variable name does not refer to an existing widget, null is returned (TODO: this
      * behaviour will probably change in the future)
+     * 
+     * <p>If the variable name contains the "/./" notation, it will return a Collection of values,
+     * using the {@link org.apache.cocoon.forms.util.WidgetFinder} utility.
      */
     public Object resolveVariable(String name) {
-        // TODO allow to access other widgets instead of only siblings (allow going up with ../ notation or something)
-        Widget widget;
-        if (!referenceChildren)
-            widget = ((ContainerWidget)this.widget.getParent()).lookupWidget(name);
-        else
-            widget = ((ContainerWidget)this.widget).lookupWidget(name);
-        if (widget != null) {
-            Object value = widget.getValue();
-
-            if (value == null && widget.isRequired()) {
-                // the widget currently has not yet a value, but since it is required, it will get a value sooner
-                // or later. Therefore, we throw an exception here indicating that this expression can currenlty
-                // not yet be evaluated, but will be at a later time.
-                throw new CannotYetResolveWarning();
+        if (name.indexOf("/./") != -1) {
+            WidgetFinder finder = new WidgetFinder(widget, name, false);
+            Collection widgets = finder.getWidgets();
+            List result = new ArrayList();
+            for (Iterator iter = widgets.iterator(); iter.hasNext();) {
+                Widget widget = (Widget) iter.next();
+                if (widget.getValue() != null) {
+                    result.add(widget.getValue());
+                } else if (widget.isRequired() || widget instanceof CalculatedField) {
+                    // the widget currently has not yet a value, but since it is required or calculated, it will get a value sooner
+                    // or later. Therefore, we throw an exception here indicating that this expression can currenlty
+                    // not yet be evaluated, but will be at a later time.
+                    throw new CannotYetResolveWarning();
+                }
             }
-
-            // do some type conversions:
-            //   * the expression library only knows about BigDecimals as being numbers, so convert Longs first to BigDecimals
-            //   * ...
-            if (value instanceof Long)
-                return new BigDecimal(((Long)value).longValue());
-            else if (value instanceof Integer)
-                return new BigDecimal(((Integer)value).intValue());
-            else if (value instanceof Number)
-                return new BigDecimal(value.toString());
+            return result;
+        } else {
+            Widget widget;
+            if (!referenceChildren)
+                widget = ((ContainerWidget)this.widget.getParent()).lookupWidget(name);
             else
-                return value;
+                widget = ((ContainerWidget)this.widget).lookupWidget(name);
+            if (widget != null) {
+                Object value = widget.getValue();
+
+                if (value == null && (widget.isRequired() || widget instanceof CalculatedField)) {
+                    // the widget currently has not yet a value, but since it is required or calculated, it will get a value sooner
+                    // or later. Therefore, we throw an exception here indicating that this expression can currenlty
+                    // not yet be evaluated, but will be at a later time.
+                    throw new CannotYetResolveWarning();
+                }
+
+                // do some type conversions:
+                //   * the expression library only knows about BigDecimals as being numbers, so convert Longs first to BigDecimals
+                //   * same for Integer
+                //   * all other Number instances will get converted to String first.
+                if (value instanceof Long)
+                    return new BigDecimal(((Long)value).longValue());
+                else if (value instanceof Integer)
+                    return new BigDecimal(((Integer)value).intValue());
+                else if (value instanceof Number)
+                    return new BigDecimal(value.toString());
+                else
+                    return value;
+            }
+            return null;            
         }
-        return null;
     }
 
     public Object get(String s) {

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithm.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithm.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithm.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithm.java Thu May 25 17:53:05 2006
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.avalon.framework.context.Context;
+import org.apache.avalon.framework.context.ContextException;
+import org.apache.avalon.framework.context.Contextualizable;
+import org.apache.avalon.framework.logger.LogEnabled;
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm;
+
+/**
+ * Abstract base class for algorithms. 
+ * 
+ * <p> This class implements the getTriggerWidgets method and holds the triggers list. It also implements
+ * Contextualizable and LogEnabled to give the algorithms access to the cocoon's ObjectModel and Loggers.
+ * </p>
+ * 
+ * @version $Id$
+ */
+public abstract class AbstractBaseAlgorithm implements CalculatedFieldAlgorithm, LogEnabled, Contextualizable {
+
+    protected List triggers = new ArrayList();
+    private Logger logger = null;
+    private Context context = null;
+    
+    public Iterator getTriggerWidgets() {
+        return triggers.iterator();
+    }
+    
+    public void addTrigger(String widget) {
+        this.triggers.add(widget);
+    }
+
+    public void enableLogging(Logger logger) {
+        this.logger = logger;
+    }
+    
+    protected Logger getLogger() {
+        return this.logger;
+    }
+
+    public void contextualize(Context context) throws ContextException {
+        this.context = context;
+    }
+    
+    protected Context getContext() {
+        return this.context;
+    }
+
+    public void clearTriggers() {
+        this.triggers.clear();
+    }
+
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithmBuilder.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithmBuilder.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithmBuilder.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/AbstractBaseAlgorithmBuilder.java Thu May 25 17:53:05 2006
@@ -0,0 +1,89 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.util.StringTokenizer;
+
+import org.apache.avalon.framework.context.Context;
+import org.apache.avalon.framework.context.ContextException;
+import org.apache.avalon.framework.context.Contextualizable;
+import org.apache.avalon.framework.logger.LogEnabled;
+import org.apache.avalon.framework.logger.Logger;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.cocoon.components.LifecycleHelper;
+import org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm;
+import org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithmBuilder;
+import org.apache.cocoon.forms.util.DomHelper;
+import org.w3c.dom.Element;
+
+/**
+ * Abstract builder for {@link org.apache.cocoon.forms.formmodel.algorithms.AbstractBaseAlgorithm}
+ * subclasses.
+ * 
+ * <p>
+ * This class parses the default triggers attribute, containing a comma separated list of widget paths
+ * as defined in {@link org.apache.cocoon.forms.util.WidgetFinder}. It also calls the LifecycleHelper
+ * so that algorithms gets their logger and context.
+ * </p>
+ * @version $Id$
+ */
+public abstract class AbstractBaseAlgorithmBuilder implements CalculatedFieldAlgorithmBuilder, LogEnabled, Contextualizable, Serviceable {
+
+    private Logger logger;
+    protected Context context;
+    private ServiceManager manager;
+
+    protected void setup(Element algorithmElement, AbstractBaseAlgorithm algorithm) throws Exception {
+        setupComponent(algorithm);        
+        setupTriggers(algorithmElement, algorithm);
+    }
+
+    protected void setupTriggers(Element algorithmElement, AbstractBaseAlgorithm algorithm) throws Exception {
+        String fields = DomHelper.getAttribute(algorithmElement, "triggers", null);
+        if (fields != null) setupTriggers(fields, algorithm);        
+    }
+    
+    protected void setupTriggers(String fields, AbstractBaseAlgorithm algorithm) {
+        algorithm.clearTriggers();
+        StringTokenizer stok = new StringTokenizer(fields, ", ");
+        while (stok.hasMoreTokens()) {
+            String fname = stok.nextToken();
+            algorithm.addTrigger(fname);
+        }        
+    }
+
+    public void enableLogging(Logger logger) {
+        this.logger = logger;
+    }
+    
+    protected Logger getLogger() {
+        return this.logger;
+    }
+
+    public void contextualize(Context context) throws ContextException {
+        this.context = context;
+    }
+    
+    public void service(ServiceManager manager) throws ServiceException {
+        this.manager = manager;
+    }
+
+    public void setupComponent(CalculatedFieldAlgorithm algorithm) throws Exception {
+        LifecycleHelper.setupComponent(algorithm, logger, context, manager, null);        
+    }
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaAlgorithmBuilder.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaAlgorithmBuilder.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaAlgorithmBuilder.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaAlgorithmBuilder.java Thu May 25 17:53:05 2006
@@ -0,0 +1,47 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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 org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm;
+import org.apache.cocoon.forms.util.DomHelper;
+import org.w3c.dom.Element;
+
+
+/**
+ * Builder for user custom {@link org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm}s.
+ * If the specified class is a {@link org.apache.cocoon.forms.formmodel.algorithms.AbstractBaseAlgorithm}
+ * subclass, the build process will be delegated to
+ *  {@link org.apache.cocoon.forms.formmodel.algorithms.AbstractBaseAlgorithmBuilder}.
+ * 
+ * @version $Id$
+ */
+public class JavaAlgorithmBuilder extends AbstractBaseAlgorithmBuilder {
+
+    public CalculatedFieldAlgorithm build(Element algorithmElement) throws Exception {
+        String clazzname = DomHelper.getAttribute(algorithmElement, "class");
+        Class clazz = Class.forName(clazzname);
+        if (AbstractBaseAlgorithm.class.isAssignableFrom(clazz)) {
+	        AbstractBaseAlgorithm algorithm = (AbstractBaseAlgorithm) clazz.newInstance();
+	        super.setup(algorithmElement, algorithm);
+	        return algorithm;
+        } else {
+	        CalculatedFieldAlgorithm algorithm = (CalculatedFieldAlgorithm) clazz.newInstance();
+	        super.setupComponent(algorithm);        	        
+	        return algorithm;            
+        }
+    }
+
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScript.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScript.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScript.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScript.java Thu May 25 17:53:05 2006
@@ -0,0 +1,78 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.avalon.framework.CascadingRuntimeException;
+import org.apache.cocoon.forms.datatype.Datatype;
+import org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm;
+import org.apache.cocoon.forms.formmodel.Form;
+import org.apache.cocoon.forms.formmodel.Widget;
+import org.apache.cocoon.forms.util.JavaScriptHelper;
+import org.mozilla.javascript.Function;
+
+/**
+ * Javascript calculated field algorithm.
+ * @see org.apache.cocoon.forms.formmodel.algorithms.JavaScriptBuilder
+ * @version $Id$
+ */
+public class JavaScript implements CalculatedFieldAlgorithm {
+
+    private Function jsfunction = null;
+    private Set triggerWidgets = new HashSet();
+    
+
+    public Object calculate(Form form, Widget parent, Datatype datatype) {
+        try {
+            // FIXME we could make it convert to the correct datatype automatically, 
+            // at least between different numbers types and eventually between numbers and strings.
+            return JavaScriptHelper.callFunction(this.jsfunction, null, new Object[]{form, parent}, null);
+        } catch (Exception e) {
+            throw new CascadingRuntimeException("Error invoking JavaScript server side calculation", e);
+        }        
+    }
+
+    public boolean isSuitableFor(Datatype dataType) {
+        return true;
+    }
+
+    /**
+     * @return Returns the jsfunction.
+     */
+    public Function getJsfunction() {
+        return jsfunction;
+    }
+    /**
+     * @param jsfunction The jsfunction to set.
+     */
+    public void setJsfunction(Function jsfunction) {
+        this.jsfunction = jsfunction;
+    }
+
+    public void addTriggerWidget(String widgetname) {
+        this.triggerWidgets.add(widgetname);
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm#getTriggerWidgets()
+     */
+    public Iterator getTriggerWidgets() {
+        return this.triggerWidgets.iterator();
+    }    
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScriptBuilder.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScriptBuilder.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScriptBuilder.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/JavaScriptBuilder.java Thu May 25 17:53:05 2006
@@ -0,0 +1,85 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.util.StringTokenizer;
+
+import org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm;
+import org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithmBuilder;
+import org.apache.cocoon.forms.util.DomHelper;
+import org.apache.cocoon.forms.util.JavaScriptHelper;
+import org.mozilla.javascript.Function;
+import org.w3c.dom.Element;
+
+/**
+ * Javascript based calculated field algorithm builder.
+ * 
+ * <p>
+ * With this algorithm the user can implement it's own algorithm directly in javascript and directly inside
+ * the form definition.
+ * </p>
+ * 
+ * <p>
+ * The syntax is as follows :
+ * <code>
+ *   &lt;fd:value type="javascript" triggers="items,price,discount"&gt;
+ *         var price = parent.lookupWidget('price').getValue();
+ *         var items = parent.lookupWidget('items').getValue();
+ *         var discount = parent.lookupWidget('discount').getValue();
+ * 
+ *         if (discount == 'completelyfree') return 0;
+ *         var total = price * items;
+ *         if (discount == 'halfprice') return total / 2;
+ * 		    return total;
+ *   &lt;/fd:value&gt;
+ * </code>
+ * </p>
+ * <p>
+ *   From inside the javascript function the following objects are accessible :
+ *   <dl>
+ *     <dt>form</dt>
+ *     <dd>The form object.</dd>
+ *     <dt>parent</dt>
+ *     <dd>The parent widget. This is very useful in repeaters, since the parent is the repeater row.</dd>
+ *     <dt>cocoon and other FOM objects</dt>
+ *     <dd>This are accessible only when flowscript is in use (see bug COCOON-1804)</dd>
+ *   </dl>
+ * </p>
+ * <p>
+ *   As you can see, the function must return the calculated value, and not set this directly in the widget. This
+ *   way the value can be converted correctly if needed.
+ * </p>
+ * @version $Id$
+ */
+public class JavaScriptBuilder implements CalculatedFieldAlgorithmBuilder {
+
+    public CalculatedFieldAlgorithm build(Element algorithmElement) throws Exception {
+        JavaScript ret = new JavaScript();
+        
+        String fields = DomHelper.getAttribute(algorithmElement, "triggers");
+        StringTokenizer stok = new StringTokenizer(fields, ", ");
+        while (stok.hasMoreTokens()) {
+            String fname = stok.nextToken();
+            ret.addTriggerWidget(fname);
+        }
+        
+        Function func = JavaScriptHelper.buildFunction(algorithmElement, "calculate", new String[]{"form", "parent"});
+        ret.setJsfunction(func);
+        
+        return ret;
+    }
+
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormula.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormula.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormula.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormula.java Thu May 25 17:53:05 2006
@@ -0,0 +1,162 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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);
+        }
+    }
+    
+    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;
+    }
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormulaBuilder.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormulaBuilder.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormulaBuilder.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/RepeatedFormulaBuilder.java Thu May 25 17:53:05 2006
@@ -0,0 +1,63 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.util.Iterator;
+
+import org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm;
+import org.apache.cocoon.forms.util.DomHelper;
+import org.outerj.expression.Expression;
+import org.w3c.dom.Element;
+
+/**
+ * Builds a {@link org.apache.cocoon.forms.formmodel.algorithms.RepeatedFormula}
+ * algorithm.
+ * <p>
+ * The syntax is as follows :
+ * <code>
+ *   &lt;fd:value type="repeatedformula" [inital-result="..."] eval="..." [triggers="..."]/&gt;
+ * </code>
+ * </p>
+ * @version $Id$
+ */
+public class RepeatedFormulaBuilder extends SimpleFormulaBuilder {
+
+    public CalculatedFieldAlgorithm build(Element algorithmElement) throws Exception {
+        RepeatedFormula ret = new RepeatedFormula();
+        String formula = DomHelper.getAttribute(algorithmElement, "eval");
+        setupExpression(formula,ret);
+        
+        // Remove formulaResult and formulaCurrent fom the list of triggers
+        Iterator iter = ret.getTriggerWidgets();
+        while (iter.hasNext()) {
+            String wdg = (String) iter.next();
+            if (wdg.equals("formulaResult") || wdg.equals("formulaCurrent")) {
+                iter.remove();
+            }
+        }
+        
+        // Set the repeat-on attribute and add it to the list of triggers
+        String repeatOn = DomHelper.getAttribute(algorithmElement, "repeat-on");
+        ret.setRepeatOn(repeatOn);
+        ret.addTrigger(repeatOn);
+        
+        String initialResult = DomHelper.getAttribute(algorithmElement, "initial-result");
+        Expression expression = setupExpression(initialResult);
+        ret.setInitialResult(expression);
+        super.setup(algorithmElement, ret);
+        return ret;
+    }
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormula.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormula.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormula.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormula.java Thu May 25 17:53:05 2006
@@ -0,0 +1,83 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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 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.outerj.expression.Expression;
+import org.outerj.expression.ExpressionContext;
+import org.outerj.expression.ExpressionException;
+
+/**
+ * An xreported expression based algorithm.
+ * <p>
+ * This algorithm can be used to write simple formulas, examples are :
+ * <ul>
+ *   <li>20% VAT calculation : eval="(amount / 100) * 20"</li>
+ *   <li>100$ volume discount : eval="If(amount > 1000,100,0)" 
+ *           (read: if amount is greater than 1000 then the result will be 100, otherwise 0)</li>
+ *   <li>Number of boxes needed to carry that number of items :  eval="Ceiling(items / boxsize)"</li>
+ *   <li>Number of items you can add before another box is needed : eval="Reminder(items,boxside)"</li>
+ * </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 SimpleFormula extends AbstractBaseAlgorithm {
+
+    protected Expression formula;
+    
+    public boolean isSuitableFor(Datatype dataType) {
+        return dataType.getTypeClass().isAssignableFrom(formula.getResultType());
+    }
+
+    public Object calculate(Form form, Widget parent, Datatype datatype) {
+        ExpressionContext ctx = new ExpressionContextImpl(parent, true);
+        try {
+            return formula.evaluate(ctx);
+        } catch (CannotYetResolveWarning w) {
+            return null;
+        } catch (ExpressionException e) {
+            throw new CascadingError("Error evaluating calculated field formula", e);
+        }
+    }
+
+    /**
+     * @return Returns the formula.
+     */
+    public Expression getFormula() {
+        return formula;
+    }
+    /**
+     * @param formula The formula to set.
+     */
+    public void setFormula(Expression formula) {
+        this.formula = formula;
+    }
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormulaBuilder.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormulaBuilder.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormulaBuilder.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/formmodel/algorithms/SimpleFormulaBuilder.java Thu May 25 17:53:05 2006
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.util.Iterator;
+import java.util.List;
+
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.cocoon.forms.expression.ExpressionManager;
+import org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm;
+import org.apache.cocoon.forms.util.DomHelper;
+import org.outerj.expression.Expression;
+import org.outerj.expression.VariableFunction;
+import org.w3c.dom.Element;
+
+/**
+ * Builds a {@link org.apache.cocoon.forms.formmodel.algorithms.SimpleFormula}
+ * algorithm.
+ * <p>
+ * The syntax is as follows :
+ * <code>
+ *   &lt;fd:value type="formula" eval="..."&gt; [triggers="..."]/&gt;
+ * </code>
+ * </p>
+ * @version $Id$
+ */
+public class SimpleFormulaBuilder extends AbstractBaseAlgorithmBuilder {
+
+    private ExpressionManager expressionManager;
+    private ServiceManager serviceManager;
+    
+    
+    public CalculatedFieldAlgorithm build(Element algorithmElement) throws Exception {
+        String formula = DomHelper.getAttribute(algorithmElement, "eval");
+        SimpleFormula ret = new SimpleFormula();
+        setupExpression(formula, ret);
+        super.setup(algorithmElement, ret);
+        return ret;
+    }
+
+    protected Expression setupExpression(String formula) throws Exception {
+        return expressionManager.parse(formula);
+    }
+    
+    protected void setupExpression(String formula, SimpleFormula algo) throws Exception {
+        Expression expression = expressionManager.parse(formula);
+        algo.setFormula(expression);
+        List vars = expressionManager.parseVariables(formula);
+        for (Iterator iter = vars.iterator(); iter.hasNext();) {
+            VariableFunction var = (VariableFunction) iter.next();
+            algo.addTrigger(var.getVariableName());
+        }
+    }
+
+    public void service(ServiceManager serviceManager) throws ServiceException {
+        this.serviceManager = serviceManager;
+        this.expressionManager = (ExpressionManager)serviceManager.lookup(ExpressionManager.ROLE);
+    }
+
+    public void dispose() {
+        this.serviceManager.release(this.expressionManager);
+        this.expressionManager = null;
+        this.serviceManager = null;
+    }    
+}

Added: cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/util/WidgetFinder.java
URL: http://svn.apache.org/viewvc/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/util/WidgetFinder.java?rev=409526&view=auto
==============================================================================
--- cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/util/WidgetFinder.java (added)
+++ cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms/util/WidgetFinder.java Thu May 25 17:53:05 2006
@@ -0,0 +1,362 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ *
+ * Licensed 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.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cocoon.forms.event.RepeaterEvent;
+import org.apache.cocoon.forms.event.RepeaterEventAction;
+import org.apache.cocoon.forms.event.RepeaterListener;
+import org.apache.cocoon.forms.event.WidgetEventMulticaster;
+import org.apache.cocoon.forms.formmodel.Repeater;
+import org.apache.cocoon.forms.formmodel.Widget;
+
+/**
+ * An utility class to manage list of widgets.
+ * 
+ * <p>
+ * The {@link org.apache.cocoon.forms.formmodel.Widget#lookupWidget(String)} method is able
+ * to only return one widget, while this class returns a list of widgets. It uses a path syntax containing a /./,
+ * <code>repeater/./foo</code>, which reperesents all the instances of the foo widget inside the repeater,
+ * one per row. Note that it also supports finding a widgets inside multi level repeaters, something like 
+ * invoices/./movements/./amount or courseYears/./exams/./preparatoryCourses/./title . 
+ * </p>
+ * <p>
+ * Class has been designed to offer good performances, since the widget list is built only once and
+ * is automatically updated when a repeater row is added or removed.
+ * {@link org.apache.cocoon.forms.event.RepeaterListener}s can be attached directly to receive notifications
+ * of widget additions or removals. 
+ * </p>
+ * <p>
+ * This class is used in {@link org.apache.cocoon.forms.formmodel.CalculatedField}s and 
+ * {@link org.apache.cocoon.forms.formmodel.CalculatedFieldAlgorithm}s. 
+ * </p>
+ * @version $Id$
+ */
+public class WidgetFinder {
+
+    private boolean keepUpdated = false;
+   
+    // Holds all the widgets not child of a repeater.
+    private List noRepeaterWidgets = null;
+    // Map repeater -> Set of Strings containing paths
+    private Map repeaterPaths = null;
+    // Map repeater -> Set of Widgets
+    private Map repeaterWidgets = null;
+    // A List of recently added widgets, will get cleared when getNewAdditions is called.
+    private List newAdditions = new ArrayList();
+    
+    private RefreshingRepeaterListener refreshingListener = new RefreshingRepeaterListener();
+    
+    private RepeaterListener listener;
+    
+    /**
+     * Searches for widgets. It will iterate on the given paths and find all
+     * corresponding widgets. If a path is in the forms repeater/* /widget
+     * then all the rows of the repeater will be iterated and subwidgets
+     * will be fetched. 
+     * @param context The context widget to start from.
+     * @param paths An iterator of Strings containing the paths.
+     * @param keepUpdated If true, listeners will be installed on repeaters
+     * to keep lists updated without polling.
+     */
+    public WidgetFinder(Widget context, Iterator paths, boolean keepUpdated) {
+        this.keepUpdated = keepUpdated;
+        while (paths.hasNext()) {
+            String path= (String)paths.next();
+            path = toAsterisk(path);
+            if (path.indexOf('*') == -1) {
+                addSimpleWidget(context, path);
+            } else {
+                recurseRepeaters(context, path, true);
+            }
+        }
+    }
+    
+    /**
+     * Searches for widgets. If path is in the forms repeater/* /widget
+     * then all the rows of the repeater will be iterated and subwidgets
+     * will be fetched. 
+     * @param context The context widget to start from.
+     * @param path Path to search for..
+     * @param keepUpdated If true, listeners will be installed on repeaters
+     * to keep lists updated without polling.
+     */
+    public WidgetFinder(Widget context, String path, boolean keepUpdated) {
+        path = toAsterisk(path);
+        this.keepUpdated = keepUpdated;
+        if (path.indexOf('*') == -1) {
+            addSimpleWidget(context, path);
+        } else {
+            recurseRepeaters(context, path, true);
+        }        
+    }
+    
+    private String toAsterisk(String path) {
+        StringBuffer pathsb = new StringBuffer(path);
+        int pos = 0;
+        while ((pos = pathsb.indexOf("/./")) != -1) {
+            pathsb.setCharAt(pos + 1, '*');
+        }
+        return pathsb.toString();
+    }
+
+    /**
+     * Recurses a repeater path with asterisk.
+     * @param context The context widget.
+     * @param path The path.
+     */
+    private void recurseRepeaters(Widget context, String path, boolean root) {
+        String reppath = path.substring(0, path.indexOf('*') - 1);
+        String childpath = path.substring(path.indexOf('*') + 2);
+        Widget wdg = context.lookupWidget(reppath);
+        if (wdg == null) {
+            if (root) {
+                throw new IllegalArgumentException("Cannot find a repeater with path " + reppath + " relative to widget " + context.getName());
+            } else {
+                return;
+            }
+        }
+        if (!(wdg instanceof Repeater)) {
+            throw new IllegalArgumentException("The widget with path " + reppath + " relative to widget " + context.getName() + " is not a repeater!");
+        }
+        Repeater repeater = (Repeater)wdg;
+        if (context instanceof Repeater.RepeaterRow) {
+            // Add this repeater to the repeater widgets
+            addRepeaterWidget((Repeater) context.getParent(), repeater);
+        }
+        
+        addRepeaterPath(repeater, childpath);
+        if (childpath.indexOf('*') != -1) {
+            for (int i = 0; i < repeater.getSize(); i++) {
+                Repeater.RepeaterRow row = repeater.getRow(i);
+                recurseRepeaters(row, childpath, false);
+            }
+        } else {
+            for (int i = 0; i < repeater.getSize(); i++) {
+                Repeater.RepeaterRow row = repeater.getRow(i);
+	            Widget okwdg = row.lookupWidget(childpath);
+	            if (okwdg != null) {
+	                addRepeaterWidget(repeater, okwdg);
+	            }
+            }
+        }
+    }
+    
+    /**
+     * Adds to the list a widget descendant of a repeater.
+     * @param repeater The repeater.
+     * @param okwdg The widget.
+     */
+    private void addRepeaterWidget(Repeater repeater, Widget okwdg) {
+        if (this.repeaterWidgets == null) this.repeaterWidgets = new HashMap();
+        Set widgets = (Set) this.repeaterWidgets.get(repeater);
+        if (widgets == null) {
+            widgets = new HashSet();
+            this.repeaterWidgets.put(repeater, widgets);
+        }
+        widgets.add(okwdg);
+        newAdditions.add(okwdg);
+    }
+
+    /**
+     * Adds a repeater monitored path.
+     * @param repeater The repeater.
+     * @param childpath The child part of the path.
+     */
+    private void addRepeaterPath(Repeater repeater, String childpath) {
+        if (this.repeaterPaths == null) this.repeaterPaths = new HashMap();
+        Set paths = (Set) this.repeaterPaths.get(repeater);
+        if (paths == null) {
+            paths = new HashSet();
+            this.repeaterPaths.put(repeater, paths); 
+            if (keepUpdated) repeater.addRepeaterListener(refreshingListener);
+        }
+        paths.add(childpath);
+    }
+
+    /**
+     * Called when a new row addition event is received from a monitored repeater.
+     * @param repeater The repeated that generated the event.
+     * @param index The new row index.
+     */
+    protected void refreshForAdd(Repeater repeater, int index) {
+        Repeater.RepeaterRow row = repeater.getRow(index);
+        if (this.repeaterPaths == null) this.repeaterPaths = new HashMap();
+        Set paths = (Set) this.repeaterPaths.get(repeater);
+        for (Iterator iter = paths.iterator(); iter.hasNext();) {
+            String path = (String) iter.next();
+            if (path.indexOf('*') != -1) {
+                recurseRepeaters(row, path, false);
+            } else {
+                Widget wdg = row.lookupWidget(path);
+                if (wdg == null) {
+                    throw new IllegalStateException("Even after row addition cannot find a widget with path " + path + " in repeater " + repeater.getName());
+                }
+                addRepeaterWidget(repeater, wdg);
+            }
+        }
+    }
+    
+    /**
+     * Called when a row deletion event is received from a monitored repeater.
+     * @param repeater The repeated that generated the event.
+     * @param index The deleted row index.
+     */
+    protected void refreshForDelete(Repeater repeater, int index) {
+        Repeater.RepeaterRow row = repeater.getRow(index);
+        Set widgets = (Set) this.repeaterWidgets.get(repeater);
+        for (Iterator iter = widgets.iterator(); iter.hasNext();) {
+            Widget widget = (Widget) iter.next();
+            boolean ischild = false;
+            Widget parent = widget.getParent();
+            while (parent != null) {
+                if (parent == row) {
+                    ischild = true;
+                    break;
+                }
+                parent = parent.getParent();
+            }
+            if (ischild) {
+                iter.remove();
+                if (widget instanceof Repeater) {
+                    if (this.repeaterPaths != null) this.repeaterPaths.remove(widget);
+                    this.repeaterWidgets.remove(widget);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called when a repeater clear event is received from a monitored repeater.
+     * @param repeater The repeated that generated the event.
+     */  
+    protected void refreshForClear(Repeater repeater) {
+        Set widgets = (Set) this.repeaterWidgets.get(repeater);
+        for (Iterator iter = widgets.iterator(); iter.hasNext();) {
+            Widget widget = (Widget) iter.next();
+            if (widget instanceof Repeater) {
+                if (this.repeaterPaths != null) this.repeaterPaths.remove(widget);
+                this.repeaterWidgets.remove(widget);
+            }
+        }
+        widgets.clear();
+    }
+    
+    /**
+     * Adds a widget not contained in a repeater.
+     * @param context
+     * @param path
+     */
+    private void addSimpleWidget(Widget context, String path) {
+        Widget widget = context.lookupWidget(path);
+        if (widget == null) throw new IllegalArgumentException("Cannot find a widget with path " + path + " relative to widget " + context.getName());
+        if (this.noRepeaterWidgets == null) this.noRepeaterWidgets = new ArrayList();
+        this.noRepeaterWidgets.add(widget);
+        newAdditions.add(widget);        
+    }
+    
+    /**
+     * Return all widgets found for the given paths.
+     * @return A Collection of {@link Widget}s.
+     */
+    public Collection getWidgets() {
+        List list = new ArrayList();
+        if (this.noRepeaterWidgets != null) list.addAll(this.noRepeaterWidgets);
+        if (this.repeaterWidgets != null) {
+	        for (Iterator iter = this.repeaterWidgets.keySet().iterator(); iter.hasNext();) {
+	            Repeater repeater = (Repeater) iter.next();
+	            list.addAll((Collection)this.repeaterWidgets.get(repeater));
+	        }
+        }
+        return list;
+    }
+    
+    /**
+     * @return true if this finder is mutable (i.e. it's monitoring some repeaters) or false if getWidgets() will always return the same list (i.e. it's not monitoring any widget).
+     */
+    public boolean isMutable() {
+        return (this.repeaterPaths != null) && this.repeaterPaths.size() > 0;
+    }
+    
+    
+    class RefreshingRepeaterListener implements RepeaterListener {
+        public void repeaterModified(RepeaterEvent event) {
+            if (event.getAction() == RepeaterEventAction.ROW_ADDED) {
+                refreshForAdd((Repeater)event.getSourceWidget(), event.getRow());
+            }
+            if (event.getAction() == RepeaterEventAction.ROW_DELETING) {
+                refreshForDelete((Repeater)event.getSourceWidget(), event.getRow());
+            }
+            if (event.getAction() == RepeaterEventAction.ROWS_CLEARING) {
+                refreshForClear((Repeater)event.getSourceWidget());
+            }
+            if (listener != null) {
+                listener.repeaterModified((RepeaterEvent)event);
+            }
+        }
+    }
+    
+    /**
+     * @return true if new widgets have been added to this list (i.e. new repeater rows have been created) since last time getNewAdditions() was called. 
+     */
+    public boolean hasNewAdditions() {
+        return this.newAdditions.size() > 0;
+    }
+    
+    /**
+     * Gets the new widgets that has been added to the list, as a consequence of new repeater rows additions, since
+     * last time this method was called or the finder was initialized. 
+     * @return A List of {@link Widget}s.
+     */
+    public List getNewAdditions() {
+        List ret = new ArrayList(newAdditions);
+        newAdditions.clear();
+        return ret;
+    }
+    
+    /**
+     * Adds a repeater listener. New widget additions or deletions will be notified thru this listener (events received
+     * from monitored repeaters will be forwarded, use {@link #getNewAdditions()} to retrieve new widgets).
+     * @param listener The listener to add.
+     */
+    public void addRepeaterListener(RepeaterListener listener) {
+        this.listener = WidgetEventMulticaster.add(this.listener, listener);
+    }
+
+    /**
+     * Removes a listener. See {@link #addRepeaterListener(RepeaterListener)}.
+     * @param listener The listener to remove.
+     */
+    public void removeRepeaterListener(RepeaterListener listener) {
+        this.listener = WidgetEventMulticaster.remove(this.listener, listener);
+    }
+
+    /**
+     * @return true if there are listeners registered on this instance. See {@link #addRepeaterListener(RepeaterListener)}.
+     */
+    public boolean hasRepeaterListeners() {
+        return this.listener != null;
+    }
+
+}



Mime
View raw message