001/*
002 * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences.
003 * Copyright (C) 2007 - JScience (http://jscience.org/)
004 * All rights reserved.
005 * 
006 * Permission to use, copy, modify, and distribute this software is
007 * freely granted, provided that this notice is preserved.
008 */
009package javax.measure;
010
011import java.math.BigDecimal;
012import java.math.MathContext;
013
014import javax.measure.converter.AddConverter;
015import javax.measure.converter.RationalConverter;
016import javax.measure.converter.UnitConverter;
017import javax.measure.quantity.Quantity;
018import javax.measure.unit.Unit;
019
020/**
021 * <p> This class represents a measure whose value is an arbitrary-precision 
022 *     decimal number.</p>
023 *     
024 * <p> When converting, applications may supply the 
025 *     <code>java.math.Context</code>:[code]
026 *         DecimalMeasure<Velocity> c = DecimalMeasure.valueOf("299792458 m/s");
027 *         DecimalMeasure<Velocity> milesPerHour = c.to(MILES_PER_HOUR, MathContext.DECIMAL128);
028 *         System.out.println(milesPerHour);
029 *         
030 *         > 670616629.3843951324266284896206156 mph
031 *     [/code]
032 *     
033 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
034 * @version 4.3, October 3, 2007
035 */
036public class DecimalMeasure<Q extends Quantity> extends Measure<BigDecimal, Q> {
037    
038    /**
039     * Holds the BigDecimal value.
040     */
041    private final BigDecimal _value;
042
043    /**
044     * Holds the unit.
045     */
046    private final Unit<Q> _unit;
047
048    /**
049     * Creates a decimal measure for the specified number stated in the 
050     * specified unit.
051     */
052    public DecimalMeasure(BigDecimal value, Unit<Q> unit) {
053        _value = value;
054        _unit = unit;
055    }
056
057    /**
058     * Returns the decimal measure for the specified number stated in the 
059     * specified unit. 
060     * 
061     * @param decimal the measurement value.
062     * @param unit the measurement unit.
063     */
064    public static <Q extends Quantity> DecimalMeasure<Q> valueOf(
065            BigDecimal decimal, Unit<Q> unit) {
066        return new DecimalMeasure<Q>(decimal, unit);
067    }
068
069    /**
070     * Returns the decimal measure for the specified textual representation.
071     * This method first reads the <code>BigDecimal</code> value, then 
072     * the unit if any (value and unit should be separated by white spaces).
073     * 
074     * @param csq the decimal measure representation (including unit if any).
075     * @throws NumberFormatException if the specified character sequence is 
076     *         not a valid representation of decimal measure.
077     */
078    @SuppressWarnings("unchecked")
079    public static <Q extends Quantity> DecimalMeasure<Q> valueOf(CharSequence csq) {
080        String str = csq.toString();
081        int numberLength = str.length();
082        int unitStartIndex = -1;
083        for (int i=0; i < str.length(); i++) {
084            if (Character.isWhitespace(str.charAt(i))) {
085                for (int j=i+1; j < str.length(); j++) {
086                    if (!Character.isWhitespace(str.charAt(j))) {
087                        unitStartIndex = j;
088                        break;
089                    }
090                }
091                numberLength = i;
092                break;
093            }
094        }
095        BigDecimal decimal = new BigDecimal(str.substring(0, numberLength));
096        Unit unit = Unit.ONE;
097        if (unitStartIndex > 0) {
098            unit = Unit.valueOf(str.substring(unitStartIndex));
099        }
100        return new DecimalMeasure<Q>(decimal, unit);
101    }
102
103    @Override
104    public Unit<Q> getUnit() {
105        return _unit;
106    }
107
108    @Override
109    public BigDecimal getValue() {
110        return _value;
111    }
112
113    /**
114     * Returns the decimal measure equivalent to this measure but stated in the 
115     * specified unit. This method will raise an ArithmeticException if the 
116     * resulting measure does not have a terminating decimal expansion.
117     * 
118     * @param unit the new measurement unit.
119     * @return the measure stated in the specified unit.
120     * @throws ArithmeticException if the converted measure value does not have
121     *         a terminating decimal expansion
122     * @see #to(Unit, MathContext)
123     */
124    @Override
125    public DecimalMeasure<Q> to(Unit<Q> unit) {
126        return to(unit, null);
127    }
128
129    /**
130     * Returns the decimal measure equivalent to this measure but stated in the 
131     * specified unit, the conversion is performed using the specified math
132     * context.
133     * 
134     * @param unit the new measurement unit.
135     * @param mathContext the mathContext used to convert 
136     *        <code>BigDecimal</code> values or <code>null</code> if none. 
137     * @return the measure stated in the specified unit.
138     * @throws ArithmeticException if the result is inexact but the
139     *         rounding mode is <code>MathContext.UNNECESSARY</code> or 
140     *         <code>mathContext.precision == 0</tt> and the quotient has a 
141     *         non-terminating decimal expansion.
142     */
143    public DecimalMeasure<Q> to(Unit<Q> unit, MathContext mathContext) {
144        if ((unit == _unit) || (unit.equals(_unit)))
145            return this;
146        UnitConverter cvtr = _unit.getConverterTo(unit);
147        if (cvtr instanceof RationalConverter) {
148            RationalConverter factor = (RationalConverter) cvtr;
149            BigDecimal dividend = BigDecimal.valueOf(factor.getDividend());
150            BigDecimal divisor = BigDecimal.valueOf(factor.getDivisor());
151            BigDecimal result = mathContext == null ?
152                    _value.multiply(dividend).divide(divisor) :
153                        _value.multiply(dividend, mathContext).divide(divisor, mathContext);
154            return new DecimalMeasure<Q>(result, unit);        
155        } else if (cvtr.isLinear()) {
156            BigDecimal factor = BigDecimal.valueOf(cvtr.convert(1.0));
157            BigDecimal result = mathContext == null ?
158                    _value.multiply(factor) : _value.multiply(factor, mathContext);
159            return new DecimalMeasure<Q>(result, unit);
160        } else if (cvtr instanceof AddConverter) {
161            BigDecimal offset = BigDecimal.valueOf(((AddConverter)cvtr).getOffset());
162            BigDecimal result = mathContext == null ?
163                    _value.add(offset) : _value.add(offset, mathContext);
164            return new DecimalMeasure<Q>(result, unit);
165        } else { // Non-linear and not an offset, convert the double value.
166            BigDecimal result = BigDecimal.valueOf(cvtr.convert(_value.doubleValue()));
167            return new DecimalMeasure<Q>(result, unit);
168        }    
169    }
170
171    public double doubleValue(Unit<Q> unit) {
172        if ((unit == _unit) || (unit.equals(_unit)))
173            return _value.doubleValue();
174        return _unit.getConverterTo(unit).convert(_value.doubleValue());            
175    }
176
177    private static final long serialVersionUID = 1L; 
178}