001/*
002 * Units of Measurement Implementation for Java SE
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.uom.se.format;
031
032import java.io.IOException;
033import java.math.BigDecimal;
034import java.text.FieldPosition;
035import java.text.Format;
036import java.text.NumberFormat;
037import java.text.ParsePosition;
038
039import javax.measure.Quantity;
040import javax.measure.Unit;
041import javax.measure.format.ParserException;
042import javax.measure.format.UnitFormat;
043
044import tec.uom.lib.common.function.Parser;
045import tec.uom.se.AbstractQuantity;
046import tec.uom.se.ComparableQuantity;
047
048/**
049 * <p>
050 * This class provides the interface for formatting and parsing {@link AbstractQuantity quantities}.
051 * </p>
052 *
053 * <p>
054 * Instances of this class should be able to format quantities stated in {@link CompoundUnit}. See {@link #formatCompound formatCompound(...)}.
055 * </p>
056 *
057 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
058 * @author <a href="mailto:units@catmedia.us">Werner Keil</a>
059 * @version 1.0, $Date: 2016-10-11 $
060 * @since 1.0
061 * 
062 */
063@SuppressWarnings("rawtypes")
064public abstract class QuantityFormat extends Format implements Parser<CharSequence, ComparableQuantity> {
065
066  /**
067   *
068   */
069  private static final long serialVersionUID = -4628006924354248662L;
070
071  /**
072   * Holds the localized format instance.
073   */
074  private static final NumberSpaceQuantityFormat LOCAL = new NumberSpaceQuantityFormat(NumberFormat.getInstance(), LocalUnitFormat.getInstance());
075
076  /**
077   * Holds the default format instance.
078   */
079  private static final DefaultQuantityFormat DEFAULT = new DefaultQuantityFormat();
080
081  /**
082   * Returns the quantity format for the default locale. The default format assumes the quantity is composed of a decimal number and a {@link Unit}
083   * separated by whitespace(s).
084   *
085   * @return <code>MeasureFormat.getInstance(NumberFormat.getInstance(), UnitFormat.getInstance())</code>
086   */
087  public static QuantityFormat getInstance() {
088    return DEFAULT;
089  }
090
091  /**
092   * Returns the quantity format using the specified number format and unit format (the number and unit are separated by one space).
093   *
094   * @param numberFormat
095   *          the number format.
096   * @param unitFormat
097   *          the unit format.
098   * @return the corresponding format.
099   */
100  public static QuantityFormat getInstance(NumberFormat numberFormat, UnitFormat unitFormat) {
101    return new NumberSpaceQuantityFormat(numberFormat, unitFormat);
102  }
103
104  /**
105   * Returns the culture invariant format based upon {@link BigDecimal} canonical format and the {@link UnitFormat#getStandardInstance() standard}
106   * unit format. This format <b>is not</b> locale-sensitive and can be used for unambiguous electronic communication of quantities together with
107   * their units without loss of information. For example: <code>"1.23456789 kg.m/s2"</code> returns
108   * <code>Quantities.getQuantity(new BigDecimal("1.23456789"), AbstractUnit.parse("kg.m/s2")));</code>
109   *
110   * @param style
111   *          the format style to apply.
112   * @return the desired format.
113   */
114  public static QuantityFormat getInstance(FormatBehavior style) {
115    switch (style) {
116      case LOCALE_NEUTRAL:
117        return DEFAULT;
118      case LOCALE_SENSITIVE:
119        return LOCAL;
120      default:
121        return DEFAULT;
122    }
123  }
124
125  /**
126   * Formats the specified quantity into an <code>Appendable</code>.
127   *
128   * @param quantity
129   *          the quantity to format.
130   * @param dest
131   *          the appendable destination.
132   * @return the specified <code>Appendable</code>.
133   * @throws IOException
134   *           if an I/O exception occurs.
135   */
136  public abstract Appendable format(Quantity<?> quantity, Appendable dest) throws IOException;
137
138  /**
139   * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the index
140   * of the <code>cursor</code> argument is updated to the index after the last character used.
141   *
142   * @param csq
143   *          the <code>CharSequence</code> to parse.
144   * @param cursor
145   *          the cursor holding the current parsing index.
146   * @return the object parsed from the specified character sub-sequence.
147   * @throws IllegalArgumentException
148   *           if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
149   */
150  public abstract ComparableQuantity<?> parse(CharSequence csq, ParsePosition cursor) throws IllegalArgumentException, ParserException;
151
152  /**
153   * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the index
154   * of the <code>cursor</code> argument is updated to the index after the last character used.
155   *
156   * @param csq
157   *          the <code>CharSequence</code> to parse.
158   * @param cursor
159   *          the cursor holding the current parsing index.
160   * @return the object parsed from the specified character sub-sequence.
161   * @throws IllegalArgumentException
162   *           if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
163   */
164  @Override
165  public abstract ComparableQuantity<?> parse(CharSequence csq) throws IllegalArgumentException, ParserException;
166
167  /**
168   * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the index
169   * of the <code>cursor</code> argument is updated to the index after the last character used.
170   * 
171   * @param csq
172   *          the <code>CharSequence</code> to parse.
173   * @param index
174   *          the current parsing index.
175   * @return the object parsed from the specified character sub-sequence.
176   * @throws IllegalArgumentException
177   *           if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
178   */
179  abstract ComparableQuantity<?> parse(CharSequence csq, int index) throws IllegalArgumentException, ParserException;
180
181  @Override
182  public final StringBuffer format(Object obj, final StringBuffer toAppendTo, FieldPosition pos) {
183    if (!(obj instanceof AbstractQuantity<?>))
184      throw new IllegalArgumentException("obj: Not an instance of Quantity");
185    if ((toAppendTo == null) || (pos == null))
186      throw new NullPointerException();
187    try {
188      return (StringBuffer) format((AbstractQuantity<?>) obj, toAppendTo);
189    } catch (IOException ex) {
190      throw new Error(ex); // Cannot happen.
191    }
192  }
193
194  @Override
195  public final Quantity<?> parseObject(String source, ParsePosition pos) {
196    try {
197      return parse(source, pos);
198    } catch (IllegalArgumentException | ParserException e) {
199      return null;
200    }
201
202  }
203
204  /**
205   * Convenience method equivalent to {@link #format(AbstractQuantity, Appendable)} except it does not raise an IOException.
206   *
207   * @param quantity
208   *          the quantity to format.
209   * @param dest
210   *          the appendable destination.
211   * @return the specified <code>StringBuilder</code>.
212   */
213  public final StringBuilder format(AbstractQuantity<?> quantity, StringBuilder dest) {
214    try {
215      return (StringBuilder) this.format(quantity, (Appendable) dest);
216    } catch (IOException ex) {
217      throw new RuntimeException(ex); // Should not happen.
218    }
219  }
220
221}