ConvertedValue.java [src/usda/weru/util] Revision: default  Date:
/*
 * ConvertedValue.java
 *
 * Created on December 28, 2005, 4:59 PM
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */

package usda.weru.util;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
    
/**
 * Stores a double value and maintains the measurement units the value may be 
 * displayed in.  A hashtable creates ties between systems of measurement such as SI
 * or US and their respective units as they apply to the stored value.
 * @author Joseph Levin
 */
    public class ConvertedValue{              
    /**
     * The base units the value is stored in.
     */
        private ConversionUnit c_baseUnits;
    /**
     * The currently active display units.  This variable is assigned as unit systems are set.
     */
        private ConversionUnit c_displayUnits;
    /**
     * Hashtable with system name / unit pairs.  Used to tie a system, such as "SI" or "US" to a measurement unit.
     */
        private Map <String, ConversionUnit> c_displayUnitsTable;
        
        public static final String PROP_VALUE = "value";
        public static final String PROP_DISPLAY_VALUE = "displayValue";
        public static final String PROP_DISPLAY_UNIT = "displayUnits";
        
        private PropertyChangeSupport c_changes;
                
    /**
     * Cached conversion factor.  This will probally need to be moved to the 
     * ConversionCalculator class.
     */
        private double c_factor;
    /**
     * Value being stored in base units.
     */
        private double c_value;        
    /**
     * Number format for toStringValue and toStringDisplayValue methods.
     */
        private String c_valueOutputFormat = "#.##";
        
        /**
         * Prepares variables and objects.
         */
        private void init(){
            c_displayUnitsTable = new HashMap <String, ConversionUnit> ();
            c_changes = new PropertyChangeSupport(this);
        }
        
        /**
         * Default constructor.  Uses a conversion factor of 1, and no units.
         */
        public ConvertedValue(){
            init();
            c_factor = 1;
        }
                
    /**
     * Recommended Constructor.  Sets the base units and adds the base units to 
     * the display table with the passed system.  The display units are set to the
     * base units.
     * @param system 
     * @param baseUnits 
     */
        public ConvertedValue(String system, String baseUnits){
            init();
            try{                
                c_baseUnits = ConversionCalculator.getUnitFromTag(baseUnits);
                if (system != null && system.length() > 0){
                    addDisplayUnits(system, c_baseUnits);
                    setDisplaySystem(system);
                }
                else{
                    c_displayUnits = c_baseUnits;
                }
                updateConversionFactor();
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }                
        
        /**
         * Returns the base value stored.
         * @return The double value stored.  This is an unconverted value.
         */
        public double getValue(){
            return c_value;
        }
        
        /**
         * Sets the base value.
         * @param value The double value to be stored.  
         */
        public void setValue(double value){
            double oldValue = c_value;
            double oldDisplay = getDisplayValue();
            c_value = value;
            
            c_changes.firePropertyChange(PROP_VALUE, oldValue, value);
            c_changes.firePropertyChange(PROP_DISPLAY_VALUE, oldDisplay, getDisplayValue());
        }
        
        /**
     * Sets the measurement system to be used when the display value is requested.
     * Use the addDisplayUnits method first to assign units to a system.
     * @param system The measurement system to display the units in.
     */
        public void setDisplaySystem(String system){
            
            ConversionUnit unit = c_displayUnitsTable.get(system);
            setDisplayUnits(unit);
//            if (unit != null){
//                double oldDisplay = getDisplayValue();
//                c_displayUnits = unit;
//                updateConversionFactor();
//                
//                c_changes.firePropertyChange(PROP_DISPLAY_VALUE, oldDisplay, getDisplayValue());
//            }
//            else{
//                //System.err.println("There are no units for the " + system + " system of measurement.");
//            }
        }
        
        /**
         * Assign units to systems.  Only one unit can be tied to a system.  For
         * example, assume the value represents area.  The base units could be
         * km^2 and then a unit could be added for SI and US display.
         * addDisplayUnits("US", "ft^2");
         * addDisplayUnits("SI", "m^2");
         *
         * Now the interface is only required to tell the ConvertedValue the 
         * desired display system.
         *
         * @param system The tag or name of the measurement system.
         * @param units The measurement units to be associated with the system 
         */
        public void addDisplayUnits(String system, ConversionUnit units){
            if (system == null || units == null) return;
            c_displayUnitsTable.put(system, units);          
        }
        
        /**
         * Assigns units to systems.  First looks up the unit from the conversion
         * calculator.
         * @param system The tag or name of the measurement system.
         * @param units The measurement units to be associated with the system 
         */
        public void addDisplayUnits(String system, String units){
            try{
                ConversionUnit unit = ConversionCalculator.getUnitFromTag(units);
                addDisplayUnits(system, unit);
            }
            catch(Exception e){
                e.printStackTrace();
            }            
        }
                                        
        /**
         * If the current base and display units are not null then the conversion factor is calcuated and cached.
         */
        private void updateConversionFactor(){
            if (c_baseUnits == null || c_displayUnits == null){
                c_factor = 1;
                return;
            }
            try{                
                c_factor = ConversionCalculator.getConversionFactor(c_baseUnits, c_displayUnits);
            }
            catch (Exception e){
                e.printStackTrace();
            }
        }
        
        /**
         * Sets the display units to the given units.  This method does not add the units to the display table.  
         * This method is used without unit systems.
         * @param units The units the value should be displayed in.
         */
        public void setDisplayUnits(String units){            
            try{                
                setDisplayUnits(ConversionCalculator.getUnitFromTag(units));                                                                                                
            }
            catch (Exception e){
                e.printStackTrace();
            }   
        }
        
        public void setDisplayUnits(ConversionUnit units){                        
            double oldDisplay = getDisplayValue();
            ConversionUnit old = c_displayUnits;
            c_displayUnits = units;
            updateConversionFactor();
            c_changes.firePropertyChange(PROP_DISPLAY_UNIT, old, c_displayUnits);
            c_changes.firePropertyChange(PROP_DISPLAY_VALUE, oldDisplay, getDisplayValue());
        }
        
        /**
         * Gets the units the value is being displayed in.  These may be the same as the base units if no conversion is being applied.
         * @return ConversionCalculator.Unit measurement unit
         */
        public ConversionUnit getDisplayUnits(){
            return c_displayUnits;
        }
        
        /**
         * Sets the base units to the given units.  This method does not affect the display table.  
         * This method is used without unit systems.  This method does not affect the stored value.
         * @param units The units the value should are stored in.
         */
        public void setBaseUnits(String units){            
            try{           
                
                setBaseUnits(ConversionCalculator.getUnitFromTag(units));
 
            }
            catch (Exception e){
                e.printStackTrace();
            }   
        }
        
        public void setBaseUnits(ConversionUnit units){  
                double oldValue = getValue();
                double oldDisplay = getDisplayValue();
                c_baseUnits = units;
                updateConversionFactor();
                c_changes.firePropertyChange(PROP_VALUE, oldValue, getValue());
                c_changes.firePropertyChange(PROP_DISPLAY_VALUE, oldDisplay, getDisplayValue());
        }
        
        /**
         * Gets the units the value is being stored in.
         * @return ConversionCalculator.Unit measurement unit
         */
        public ConversionUnit getBaseUnits(){
            return c_baseUnits;
        }
        
        /**
         * The value converted from the base units to the display units.
         * @return double Converted value
         */
        public double getDisplayValue(){
            return c_value * c_factor;
        }
        
        /**
     * This method converts the passed value from the display units to the base units and stores
     * the value.
     * @param value value in display units
     */
        public void setDisplayValue(double value){            
            setValue(value / c_factor);
        }
        
        /**
         * String representation of the display value.  Applies the output format if possible.  Default format is #.##.
         * @return String The value in display units.
         */
        public String toStringDisplayValue(){
            if (c_valueOutputFormat.length() > 0){
                try{
                    return new DecimalFormat(c_valueOutputFormat).format(getDisplayValue());
                }
                catch (NumberFormatException nfe){
                    return Double.toString(getDisplayValue());
                }
            }
            else{
                return Double.toString(getDisplayValue());
            }
        }
        
        /**
         * String representation of the base value.  Applies the output format if possible.  Default format is #.##.
         * @return String The value in base units.
         */
        public String toStringValue(){
            if (c_valueOutputFormat.length() > 0){
                try{
                    return new DecimalFormat(c_valueOutputFormat).format(getValue());
                }
                catch (NumberFormatException nfe){
                    return Double.toString(getValue());
                }
            }
            else{
                return Double.toString(getValue());
            }
        }
        
        /**
         * String representation of the base value.  Does not apply the output format.
         * @return String The value in base units.
         */
        public String toString(){
            return Double.toString(getValue());
        }
        
        /**
     * Sets the output format used for the toStringValue and toStringDisplayValue methods.  This
     * format is intended to prevent too many digits after the decimal point when writing
     * @param format the number format to apply to output.
     */
        public void setOutputFormat(String format){
            c_valueOutputFormat = format;
        }         
        
        public void addPropertyChangeListener(PropertyChangeListener listener){
            c_changes.addPropertyChangeListener(listener);
        }
        
        public void addPropertyChangeListener(String property, PropertyChangeListener listener){
            c_changes.addPropertyChangeListener(property, listener);
        }
        
        public void removePropertyChangeListener(PropertyChangeListener listener){
            c_changes.removePropertyChangeListener(listener);
        }
        
        public void removePropertyChangeListener(String property, PropertyChangeListener listener){
            c_changes.removePropertyChangeListener(property, listener);
        }
    }