CalculatedField.java :  » Content-Management-System » apache-lenya-2.0 » org » apache » cocoon » forms » formmodel » Java Open Source

Java Open Source » Content Management System » apache lenya 2.0 
apache lenya 2.0 » org » apache » cocoon » forms » formmodel » CalculatedField.java
/*
 * 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: CalculatedField.java 449149 2006-09-23 03:58:05Z crossley $
 */
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;
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.