001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2016, Jean-Marie Dautelle, Werner Keil, V2COM.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-363 nor the names of its contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tec.units.ri.format;
031
032import java.io.IOException;
033
034import javax.measure.MeasurementException;
035import javax.measure.Quantity;
036import javax.measure.Unit;
037import javax.measure.format.ParserException;
038import tec.units.ri.AbstractQuantity;
039import tec.units.ri.AbstractUnit;
040import tec.units.ri.quantity.NumberQuantity;
041import tec.uom.lib.common.function.Parser;
042
043/**
044 * <p>
045 * This class provides the interface for formatting and parsing {@link Quantity quantities}.
046 * </p>
047 * 
048 * <p>
049 * Instances of this class should be able to format quantities stated in {@link CompoundUnit}. See {@link #formatCompound formatCompound(...)}.
050 * </p>
051 * 
052 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
053 * @author <a href="mailto:units@catmedia.us">Werner Keil</a>
054 * @version 0.8.4, $Date: 2016-04-20 $
055 */
056@SuppressWarnings("rawtypes")
057public abstract class QuantityFormat implements Parser<CharSequence, Quantity> {
058
059  /**
060   * 
061   */
062  // private static final long serialVersionUID = -4628006924354248662L;
063
064  /**
065   * Holds the default format instance.
066   */
067  private static final QuantityFormat DEFAULT = new Standard();
068
069  /**
070   * Holds the Number-Space-Unit format instance.
071   */
072  // private static final QuantityFormat NUM_SPACE = new NumberSpaceUnit(NumberFormat.getInstance(), SimpleUnitFormat.getInstance());
073
074  // TODO use it as an option (after fixing parse())
075
076  /**
077   * Returns the quantity format for the default locale. The default format assumes the quantity is composed of a decimal number and a {@link Unit}
078   * separated by whitespace(s).
079   * 
080   * @return <code>MeasureFormat.getInstance(NumberFormat.getInstance(), UnitFormat.getInstance())</code>
081   */
082  public static QuantityFormat getInstance() {
083    return DEFAULT;
084  }
085
086  /**
087   * Formats the specified quantity into an <code>Appendable</code>.
088   * 
089   * @param quantity
090   *          the quantity to format.
091   * @param dest
092   *          the appendable destination.
093   * @return the specified <code>Appendable</code>.
094   * @throws IOException
095   *           if an I/O exception occurs.
096   */
097  public abstract Appendable format(Quantity<?> quantity, Appendable dest) throws IOException;
098
099  /**
100   * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the index
101   * of the <code>cursor</code> argument is updated to the index after the last character used.
102   * 
103   * @param csq
104   *          the <code>CharSequence</code> to parse.
105   * @param index
106   *          the current parsing index.
107   * @return the object parsed from the specified character sub-sequence.
108   * @throws IllegalArgumentException
109   *           if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
110   */
111  abstract Quantity<?> parse(CharSequence csq, int index) throws IllegalArgumentException, ParserException;
112
113  /**
114   * Convenience method equivalent to {@link #format(AbstractQuantity, Appendable)} except it does not raise an IOException.
115   * 
116   * @param quantity
117   *          the quantity to format.
118   * @param dest
119   *          the appendable destination.
120   * @return the specified <code>StringBuilder</code>.
121   */
122  public final StringBuilder format(Quantity<?> q, StringBuilder dest) {
123    try {
124      return (StringBuilder) this.format(q, (Appendable) dest);
125    } catch (IOException ex) {
126      throw new MeasurementException(ex); // Should not happen.
127    }
128  }
129
130  /**
131   * Formats an object to produce a string. This is equivalent to <blockquote> {@link #format(Unit, StringBuilder) format}<code>(unit,
132   *         new StringBuilder()).toString();</code> </blockquote>
133   *
134   * @param obj
135   *          The object to format
136   * @return Formatted string.
137   * @exception IllegalArgumentException
138   *              if the Format cannot format the given object
139   */
140  public final String format(Quantity q) {
141    if (q instanceof AbstractQuantity) {
142      return format((AbstractQuantity<?>) q, new StringBuilder()).toString();
143    } else {
144      return (this.format(q, new StringBuilder())).toString();
145    }
146  }
147
148  static int getFractionDigitsCount(double d) {
149    if (d >= 1) { // we only need the fraction digits
150      d = d - (long) d;
151    }
152    if (d == 0) { // nothing to count
153      return 0;
154    }
155    d *= 10; // shifts 1 digit to left
156    int count = 1;
157    while (d - (long) d != 0) { // keeps shifting until there are no more
158      // fractions
159      d *= 10;
160      count++;
161    }
162    return count;
163  }
164
165  // Holds standard implementation.
166  private static final class Standard extends QuantityFormat {
167
168    /**
169     * 
170     */
171    // private static final long serialVersionUID = 2758248665095734058L;
172
173    @Override
174    public Appendable format(Quantity q, Appendable dest) throws IOException {
175      Unit unit = q.getUnit();
176      // if (unit instanceof CompoundUnit)
177      // return formatCompound(q.doubleValue(unit),
178      // (CompoundUnit) unit, dest);
179      // else {
180
181      Number number = q.getValue();
182      dest.append(number.toString());
183      // }
184      if (q.getUnit().equals(AbstractUnit.ONE))
185        return dest;
186      dest.append(' ');
187      return SimpleUnitFormat.getInstance().format(unit, dest);
188      // }
189    }
190
191    @SuppressWarnings("unchecked")
192    @Override
193    Quantity<?> parse(CharSequence csq, int index) throws ParserException {
194      int startDecimal = index; // cursor.getIndex();
195      while ((startDecimal < csq.length()) && Character.isWhitespace(csq.charAt(startDecimal))) {
196        startDecimal++;
197      }
198      int endDecimal = startDecimal + 1;
199      while ((endDecimal < csq.length()) && !Character.isWhitespace(csq.charAt(endDecimal))) {
200        endDecimal++;
201      }
202      Double decimal = new Double(csq.subSequence(startDecimal, endDecimal).toString());
203      // cursor.setIndex(endDecimal + 1);
204      int startUnit = endDecimal + 1;// csq.toString().indexOf(' ') + 1;
205      Unit unit = SimpleUnitFormat.getInstance().parse(csq, startUnit);
206      return NumberQuantity.of(decimal.doubleValue(), unit);
207    }
208
209    public Quantity<?> parse(CharSequence csq) throws ParserException {
210      return parse(csq, 0);
211    }
212  }
213}