Skip to content

Commit

Permalink
Calculated fields
Browse files Browse the repository at this point in the history
  • Loading branch information
SimoneGianni committed May 26, 2006
1 parent 072cfb8 commit f3f9db2
Show file tree
Hide file tree
Showing 19 changed files with 1,700 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -60,13 +61,19 @@ public void configure(Configuration config) throws ConfigurationException {
}

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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,5 +32,21 @@ public interface ExpressionManager {

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;
}
85 changes: 85 additions & 0 deletions src/main/java/org/apache/cocoon/forms/expression/SumFunction.java
Original file line number Diff line number Diff line change
@@ -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";
}

}
200 changes: 200 additions & 0 deletions src/main/java/org/apache/cocoon/forms/formmodel/CalculatedField.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit f3f9db2

Please sign in to comment.