001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2017, 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 1.0, $Date: 2016-04-20 $
055 * @since 1.0
056 */
057@SuppressWarnings("rawtypes")
058public abstract class QuantityFormat implements Parser<CharSequence, Quantity> {
059
060  /**
061   * 
062   */
063  // private static final long serialVersionUID = -4628006924354248662L;
064
065  /**
066   * Holds the default format instance.
067   */
068  private static final QuantityFormat DEFAULT = new Standard();
069
070  /**
071   * Holds the Number-Space-Unit format instance.
072   */
073  // private static final QuantityFormat NUM_SPACE = new NumberSpaceUnit(NumberFormat.getInstance(), SimpleUnitFormat.getInstance());
074
075  // TODO use it as an option (after fixing parse())
076
077  /**
078   * Returns the quantity format for the default locale. The default format assumes the quantity is composed of a decimal number and a {@link Unit}
079   * separated by whitespace(s).
080   * 
081   * @return <code>MeasureFormat.getInstance(NumberFormat.getInstance(), UnitFormat.getInstance())</code>
082   */
083  public static QuantityFormat getInstance() {
084    return DEFAULT;
085  }
086
087  /**
088   * Formats the specified quantity into an <code>Appendable</code>.
089   * 
090   * @param quantity
091   *          the quantity to format.
092   * @param dest
093   *          the appendable destination.
094   * @return the specified <code>Appendable</code>.
095   * @throws IOException
096   *           if an I/O exception occurs.
097   */
098  public abstract Appendable format(Quantity<?> quantity, Appendable dest) throws IOException;
099
100  /**
101   * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the index
102   * of the <code>cursor</code> argument is updated to the index after the last character used.
103   * 
104   * @param csq
105   *          the <code>CharSequence</code> to parse.
106   * @param index
107   *          the current parsing index.
108   * @return the object parsed from the specified character sub-sequence.
109   * @throws IllegalArgumentException
110   *           if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
111   */
112  abstract Quantity<?> parse(CharSequence csq, int index) throws IllegalArgumentException, ParserException;
113
114  /**
115   * Convenience method equivalent to {@link #format(AbstractQuantity, Appendable)} except it does not raise an IOException.
116   * 
117   * @param q
118   *          the quantity to format.
119   * @param dest
120   *          the appendable destination.
121   * @return the specified <code>StringBuilder</code>.
122   */
123  public final StringBuilder format(Quantity<?> q, StringBuilder dest) {
124    try {
125      return (StringBuilder) this.format(q, (Appendable) dest);
126    } catch (IOException ex) {
127      throw new MeasurementException(ex); // Should not happen.
128    }
129  }
130
131  /**
132   * Formats an object to produce a string. This is equivalent to <blockquote> {@link #format(Unit, StringBuilder) format}<code>(unit,
133   *         new StringBuilder()).toString();</code> </blockquote>
134   *
135   * @param obj
136   *          The object to format
137   * @return Formatted string.
138   * @exception IllegalArgumentException
139   *              if the Format cannot format the given object
140   */
141  public final String format(Quantity q) {
142    if (q instanceof AbstractQuantity) {
143      return format((AbstractQuantity<?>) q, new StringBuilder()).toString();
144    } else {
145      return (this.format(q, new StringBuilder())).toString();
146    }
147  }
148
149  static int getFractionDigitsCount(double d) {
150    if (d >= 1) { // we only need the fraction digits
151      d = d - (long) d;
152    }
153    if (d == 0) { // nothing to count
154      return 0;
155    }
156    d *= 10; // shifts 1 digit to left
157    int count = 1;
158    while (d - (long) d != 0) { // keeps shifting until there are no more
159      // fractions
160      d *= 10;
161      count++;
162    }
163    return count;
164  }
165
166  // Holds standard implementation.
167  private static final class Standard extends QuantityFormat {
168
169    /**
170     * 
171     */
172    // private static final long serialVersionUID = 2758248665095734058L;
173
174    @Override
175    public Appendable format(Quantity q, Appendable dest) throws IOException {
176      Unit unit = q.getUnit();
177      // if (unit instanceof CompoundUnit)
178      // return formatCompound(q.doubleValue(unit),
179      // (CompoundUnit) unit, dest);
180      // else {
181
182      Number number = q.getValue();
183      dest.append(number.toString());
184      // }
185      if (q.getUnit().equals(AbstractUnit.ONE))
186        return dest;
187      dest.append(' ');
188      return SimpleUnitFormat.getInstance().format(unit, dest);
189      // }
190    }
191
192    @SuppressWarnings("unchecked")
193    @Override
194    Quantity<?> parse(CharSequence csq, int index) throws ParserException {
195      int startDecimal = index; // cursor.getIndex();
196      while ((startDecimal < csq.length()) && Character.isWhitespace(csq.charAt(startDecimal))) {
197        startDecimal++;
198      }
199      int endDecimal = startDecimal + 1;
200      while ((endDecimal < csq.length()) && !Character.isWhitespace(csq.charAt(endDecimal))) {
201        endDecimal++;
202      }
203      Double decimal = new Double(csq.subSequence(startDecimal, endDecimal).toString());
204      // cursor.setIndex(endDecimal + 1);
205      int startUnit = endDecimal + 1;// csq.toString().indexOf(' ') + 1;
206      Unit unit = SimpleUnitFormat.getInstance().parse(csq, startUnit);
207      return NumberQuantity.of(decimal.doubleValue(), unit);
208    }
209
210    public Quantity<?> parse(CharSequence csq) throws ParserException {
211      return parse(csq, 0);
212    }
213  }
214}