blob: 3be35dbbee9da806cf2eac6ea29e0406b25d94ed [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.forms.formmodel;
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;
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;
}
}