/* * 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; } }