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.text.FieldPosition;
013import java.text.Format;
014import java.text.NumberFormat;
015import java.text.ParseException;
016import java.text.ParsePosition;
017
018import javax.measure.unit.CompoundUnit;
019import javax.measure.unit.Unit;
020import javax.measure.unit.UnitFormat;
021
022/**
023 * <p> This class provides the interface for formatting and parsing {@link 
024 *     Measure measures}.</p>
025 *     
026 * <p> As a minimum, instances of this class should be able to parse/format
027 *     measure using {@link CompoundUnit}. </p>    
028 *
029 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
030 * @version 4.2, August 26, 2007
031 */
032public abstract class MeasureFormat extends Format {
033   
034    /**
035     * Returns the measure format for the default locale.
036     * 
037     *  @return <code>getInstance(Number.getInstance(), Unit.getInstance())</code>
038     */
039    public static MeasureFormat getInstance() {
040        return DEFAULT;
041    }
042
043    static final NumberUnit DEFAULT = new NumberUnit(NumberFormat
044            .getInstance(), UnitFormat.getInstance());
045
046    /**
047     * Returns the measure format using the specified number format and 
048     * unit format (the number and unit are separated by a space).
049     * 
050     * @param numberFormat the number format.
051     * @param unitFormat the unit format.
052     * @return the corresponding format.
053     */
054    public static MeasureFormat getInstance(NumberFormat numberFormat,
055            UnitFormat unitFormat) {
056        return new NumberUnit(numberFormat, unitFormat);
057    }
058
059    // Holds default implementation.
060    static final class NumberUnit extends MeasureFormat {
061        private final NumberFormat _numberFormat;
062
063        private final UnitFormat _unitFormat;
064
065        private NumberUnit(NumberFormat numberFormat, UnitFormat unitFormat) {
066            _numberFormat = numberFormat;
067            _unitFormat = unitFormat;
068        }
069
070        @Override
071        public StringBuffer format(Object obj, StringBuffer toAppendTo,
072                FieldPosition pos) {
073            Measure<?, ?> measure = (Measure<?, ?>) obj;
074            Object value = measure.getValue();
075            Unit<?> unit = measure.getUnit();
076            if (value instanceof Number) {
077                if (unit instanceof CompoundUnit)
078                    return formatCompound(((Number) value).doubleValue(),
079                            (CompoundUnit<?>) unit, toAppendTo, pos);
080                _numberFormat.format(value, toAppendTo, pos);
081            } else {
082                toAppendTo.append(value);
083            }
084            if (!measure.getUnit().equals(Unit.ONE)) {
085                toAppendTo.append(' ');
086                _unitFormat.format(unit, toAppendTo, pos);
087            }
088            return toAppendTo;
089        }
090
091        // Measure using Compound unit have no separators in their representation.
092        StringBuffer formatCompound(double value, Unit<?> unit,
093                StringBuffer toAppendTo, FieldPosition pos) {
094            if (!(unit instanceof CompoundUnit)) {
095                toAppendTo.append((long) value);
096                return _unitFormat.format(unit, toAppendTo, pos);
097            }
098            Unit<?> high = ((CompoundUnit<?>) unit).getHigher();
099            Unit<?> low = ((CompoundUnit<?>) unit).getLower(); // The unit in which the value is stated.
100            long highValue = (long) low.getConverterTo(high).convert(value);
101            double lowValue = value
102                    - high.getConverterTo(low).convert(highValue);
103            formatCompound(highValue, high, toAppendTo, pos);
104            formatCompound(lowValue, low, toAppendTo, pos);
105            return toAppendTo;
106        }
107
108        @Override
109        public Object parseObject(String source, ParsePosition pos) {
110            int start = pos.getIndex();
111            try {
112                int i = start;
113                Number value = _numberFormat.parse(source, pos);
114                if (i == pos.getIndex())
115                    return null; // Cannot parse.
116                i = pos.getIndex();
117                if (i >= source.length())
118                    return measureOf(value, Unit.ONE); // No unit.
119                boolean isCompound = !Character.isWhitespace(source.charAt(i));
120                if (isCompound)
121                    return parseCompound(value, source, pos);
122                if (++i >= source.length())
123                    return measureOf(value, Unit.ONE); // No unit.
124                pos.setIndex(i); // Skips separator.
125                Unit<?> unit = _unitFormat.parseProductUnit(source, pos);
126                return measureOf(value, unit);
127            } catch (ParseException e) {
128                pos.setIndex(start);
129                pos.setErrorIndex(e.getErrorOffset());
130                return null;
131            }
132        }
133
134        @SuppressWarnings("unchecked")
135        private Object parseCompound(Number highValue, String source,
136                ParsePosition pos) throws ParseException {
137            Unit high = _unitFormat.parseSingleUnit(source, pos);
138            int i = pos.getIndex();
139            if (i >= source.length()
140                    || Character.isWhitespace(source.charAt(i)))
141                return measureOf(highValue, high);
142            Measure lowMeasure = (Measure) parseObject(source, pos);
143            Unit unit = lowMeasure.getUnit();
144            long l = lowMeasure.longValue(unit)
145                    + (long) high.getConverterTo(unit).convert(
146                            highValue.longValue());
147            return Measure.valueOf(l, unit);
148        }
149
150        @SuppressWarnings("unchecked")
151        private static Measure measureOf(Number value, Unit unit) {
152            if (value instanceof Double) {
153                return Measure.valueOf(value.doubleValue(), unit);
154            } else if (value instanceof Long) {
155                return Measure.valueOf(value.longValue(), unit);
156            } else if (value instanceof Float) {
157                return Measure.valueOf(value.floatValue(), unit);
158            } else if (value instanceof Integer) {
159                return Measure.valueOf(value.intValue(), unit);
160            } else if (value instanceof BigDecimal) {
161                return DecimalMeasure.valueOf((BigDecimal) value, unit);
162            } else {
163                return Measure.valueOf(value.doubleValue(), unit);
164            }
165        }
166
167        private static final long serialVersionUID = 1L;
168    }
169}