001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2021, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
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-385, Indriya nor the names of their 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 tech.units.indriya.format;
031
032import java.io.IOException;
033import java.text.FieldPosition;
034import java.text.Format;
035import java.text.ParsePosition;
036
037import javax.measure.MeasurementException;
038import javax.measure.Quantity;
039import javax.measure.Unit;
040import javax.measure.format.MeasurementParseException;
041import javax.measure.format.QuantityFormat;
042import tech.units.indriya.ComparableQuantity;
043import tech.units.indriya.quantity.CompoundQuantity;
044import tech.units.indriya.quantity.MixedQuantity;
045import tech.uom.lib.common.function.Parser;
046
047/**
048 * <p>
049 * This class provides the interface for formatting and parsing {@link Quantity quantities}.
050 * </p>
051 *
052 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
053 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
054 * @version 2.0, $Date: 2020-02-23 $
055 * @since 1.0
056 * 
057 */
058@SuppressWarnings("rawtypes")
059public abstract class AbstractQuantityFormat extends Format implements QuantityFormat, Parser<CharSequence, Quantity> {
060    /**
061     * The default delimiter.
062     */
063    protected static final String DEFAULT_DELIMITER = " ";
064
065    /**
066     *
067     */
068    private static final long serialVersionUID = -4628006924354248662L;
069
070    /**
071     * Formats the specified quantity into an <code>Appendable</code>.
072     *
073     * @param quantity
074     *            the quantity to format.
075     * @param dest
076     *            the appendable destination.
077     * @return the specified <code>Appendable</code>.
078     * @throws IOException
079     *             if an I/O exception occurs.
080     */
081    public abstract Appendable format(Quantity<?> quantity, Appendable dest) throws IOException;
082
083    /**
084     * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the
085     * index of the <code>cursor</code> argument is updated to the index after the last character used.
086     *
087     * @param csq
088     *            the <code>CharSequence</code> to parse.
089     * @param cursor
090     *            the cursor holding the current parsing index.
091     * @return the object parsed from the specified character sub-sequence.
092     * @throws IllegalArgumentException
093     *             if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
094     */
095    public abstract Quantity<?> parse(CharSequence csq, ParsePosition cursor) throws IllegalArgumentException, MeasurementParseException;
096
097    /**
098     * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the
099     * index of the <code>cursor</code> argument is updated to the index after the last character used.
100     *
101     * @param csq
102     *            the <code>CharSequence</code> to parse.
103     * @return the object parsed from the specified character sub-sequence.
104     * @throws IllegalArgumentException
105     *             if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
106     */
107    @Override
108    public abstract Quantity<?> parse(CharSequence csq) throws MeasurementParseException;
109
110    /**
111     * Parses a portion of the specified <code>CharSequence</code> from the specified position to produce an object. If parsing succeeds, then the
112     * index of the <code>cursor</code> argument is updated to the index after the last character used.
113     * 
114     * @param csq
115     *            the <code>CharSequence</code> to parse.
116     * @param index
117     *            the current parsing index.
118     * @return the object parsed from the specified character sub-sequence.
119     * @throws IllegalArgumentException
120     *             if any problem occurs while parsing the specified character sequence (e.g. illegal syntax).
121     */
122    protected abstract Quantity<?> parse(CharSequence csq, int index) throws IllegalArgumentException, MeasurementParseException;
123
124    @Override
125    public final StringBuffer format(Object obj, final StringBuffer toAppendTo, FieldPosition pos) {
126        if (obj instanceof MixedQuantity<?>) {
127                return formatMixed((MixedQuantity<?>) obj, toAppendTo);
128        } else if(obj instanceof CompoundQuantity<?>) { // TODO remove with next release
129            return formatCompound((CompoundQuantity<?>) obj, toAppendTo);
130        } else {
131            if (!(obj instanceof ComparableQuantity<?>))
132                throw new IllegalArgumentException("obj: Not an instance of Quantity");
133            if ((toAppendTo == null) || (pos == null))
134                throw new NullPointerException();
135            return (StringBuffer) format((ComparableQuantity<?>) obj, toAppendTo);
136        }
137    }
138    
139    @Override
140    public final Quantity<?> parseObject(String source, ParsePosition pos) {
141        try {
142            return parse(source, pos);
143        } catch (IllegalArgumentException | MeasurementParseException e) {
144            return null;
145        }
146    }
147
148    /**
149     * Formats an object to produce a string. This is equivalent to <blockquote> {@link #format(Unit, StringBuilder) format}<code>(unit,
150     *         new StringBuilder()).toString();</code> </blockquote>
151     *
152     * @param quantity
153     *          The quantity to format
154     * @return Formatted string.
155     */
156    public final String format(Quantity<?> quantity) {
157      if (quantity instanceof ComparableQuantity) return format((ComparableQuantity<?>) quantity, new StringBuffer()).toString();
158
159      try {
160        return (this.format(quantity, new StringBuffer())).toString();
161      } catch (IOException ex) {
162        throw new MeasurementException(ex); // Should never happen.
163      }
164    }
165    
166    /**
167     * Convenience method equivalent to {@link #format(ComparableQuantity, Appendable)} except it does not raise an IOException.
168     *
169     * @param quantity
170     *            the quantity to format.
171     * @param dest
172     *            the appendable destination.
173     * @return the specified <code>StringBuilder</code>.
174     */
175    protected final StringBuffer format(ComparableQuantity<?> quantity, StringBuffer dest) {
176        try {
177            return (StringBuffer) this.format(quantity, (Appendable) dest);
178        } catch (IOException ex) {
179            throw new MeasurementException(ex); // Should not happen.
180        }
181    }
182    
183    /**
184     * Convenience method equivalent to {@link #format(MixedQuantity, Appendable)} except it does not raise an IOException.
185     *
186     * @param mixed
187     *            the mixed quantity to format.
188     * @param dest
189     *            the appendable destination.
190     * @return the specified <code>StringBuilder</code>.
191     */
192    protected abstract StringBuffer formatMixed(MixedQuantity<?> mixed, StringBuffer dest);
193
194    
195    /**
196     * Convenience method equivalent to {@link #format(CompoundQuantity, Appendable)} except it does not raise an IOException.
197     *
198     * @param comp
199     *            the composite quantity to format.
200     * @param dest
201     *            the appendable destination.
202     * @return the specified <code>StringBuilder</code>.
203     * @deprecated use #formatMixed
204     */
205    protected abstract StringBuffer formatCompound(CompoundQuantity<?> comp, StringBuffer dest);
206}