You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by si...@apache.org on 2006/05/26 02:53:06 UTC
svn commit: r409526 - in
/cocoon/trunk/blocks/cocoon-forms/cocoon-forms-impl/src/main/java/org/apache/cocoon/forms:
expression/ formmodel/ formmodel/algorithms/ util/
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>
+ * <fd:calculatedfield id="id" [state="{invisible|output|disabled|active}"]>
+ * <fd:datatype base="...">...</fd:datatype>
+ * <fd:label>...</fd:label>
+ * <fd:value type="...">...</fd:algorithm>
+ * </fd:calculatedfield>
+ * </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>
+ * <fd:value type="javascript" triggers="items,price,discount">
+ * 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;
+ * </fd:value>
+ * </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>
+ * <fd:value type="repeatedformula" [inital-result="..."] eval="..." [triggers="..."]/>
+ * </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>
+ * <fd:value type="formula" eval="..."> [triggers="..."]/>
+ * </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;
+ }
+
+}