001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2020, Units of Measurement project.
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 static tech.units.indriya.format.CommonFormatter.parseCompoundAsLeading;
033
034import java.io.IOException;
035import java.text.NumberFormat;
036import java.text.ParsePosition;
037
038import javax.measure.Quantity;
039import javax.measure.Unit;
040import javax.measure.format.MeasurementParseException;
041
042import tech.units.indriya.AbstractUnit;
043import tech.units.indriya.internal.format.RationalNumberScanner;
044import tech.units.indriya.quantity.CompoundQuantity;
045import tech.units.indriya.quantity.Quantities;
046
047/**
048 * A simple implementation of QuantityFormat
049 * 
050 * <p>
051 * The following pattern letters are defined:
052 * <blockquote>
053 * <table class="striped">
054 * <caption style="display:none">Chart shows pattern letters, date/time component, presentation, and examples.</caption>
055 * <thead>
056 *     <tr>
057 *         <th style="text-align:left">Letter
058 *         <th style="text-align:left">Quantity Component
059 *         <th style="text-align:left">Presentation
060 *         <th style="text-align:left">Examples
061 * </thead>
062 * <tbody>
063 *     <tr>
064 *         <td><code>n</code>
065 *         <td>Numeric value
066 *         <td><a href="#number">Number</a>
067 *         <td><code>27</code>
068 *     <tr>
069 *         <td><code>u</code>
070 *         <td>Unit
071 *         <td><a href="#text">Text</a>
072 *         <td><code>m</code>
073 *    <tr>
074 *         <td><code>~</code>
075 *         <td>Mixed radix
076 *         <td><a href="#text">Text</a>
077 *         <td><code>1 m</code>; 27 <code>cm</code>
078 * </tbody>
079 * </table>
080 * </blockquote>
081 * Pattern letters are usually repeated, as their number determines the
082 * exact presentation:
083 * <ul>
084 * <li><strong><a id="text">Text:</a></strong>
085 *     For formatting, if the number of pattern letters is 4 or more,
086 *     the full form is used; otherwise a short or abbreviated form
087 *     is used if available.
088 *     For parsing, both forms are accepted, independent of the number
089 *     of pattern letters.<br><br></li>
090 * <li><strong><a id="number">Number:</a></strong>
091 *     For formatting, the number of pattern letters is the minimum
092 *     number of digits, and shorter numbers are zero-padded to this amount.
093 *     For parsing, the number of pattern letters is ignored unless
094 *     it's needed to separate two adjacent fields.<br><br></li>
095 *     
096 *<li><strong><a id="radix">Mixed Radix:</a></strong>
097 *     The Mixed radix marker <code>"~"</code> is followed by a character sequence acting as mixed radix delimiter. This character sequence must not contain <code>"~"</code> itself or any numeric values.<br></li>
098 * </ul>
099 * </p>
100 * @version 1.4, $Date: 2019-04-14 $
101 * @since 2.0
102 */
103@SuppressWarnings("rawtypes")
104public class SimpleQuantityFormat extends AbstractQuantityFormat {
105        /**
106         * Holds the default format instance.
107         */
108        private static final SimpleQuantityFormat DEFAULT = new SimpleQuantityFormat();
109
110        private static final String NUM_PART = "n";
111        private static final String UNIT_PART = "u";
112        private static final String RADIX = "~";
113        
114        /**
115         * The pattern string of this formatter. This is always a non-localized pattern.
116         * May not be null. See class documentation for details.
117         * 
118         * @serial
119         */
120        private final String pattern;
121        
122        private String delimiter;
123        
124        private String mixDelimiter;
125
126        /**
127         *
128         */
129        private static final long serialVersionUID = 2758248665095734058L;
130
131        /**
132         * Constructs a <code>SimpleQuantityFormat</code> using the given pattern.
133         * <p>
134         * 
135         * @param pattern
136         *            the pattern describing the quantity and unit format
137         * @exception NullPointerException
138         *                if the given pattern is null
139         * @exception IllegalArgumentException
140         *                if the given pattern is invalid
141         */
142        public SimpleQuantityFormat(String pattern) {
143                this.pattern = pattern;
144                if (pattern != null && !pattern.isEmpty()) {
145                   if (pattern.contains(RADIX)) {
146                       final String singlePattern = pattern.substring(0, pattern.indexOf(RADIX));
147                       mixDelimiter = pattern.substring(pattern.indexOf(RADIX) + 1);
148                       delimiter = singlePattern.substring(pattern.indexOf(NUM_PART)+1, pattern.indexOf(UNIT_PART));
149                   } else {
150                       delimiter = pattern.substring(pattern.indexOf(NUM_PART)+1, pattern.indexOf(UNIT_PART));
151                   }
152                }
153        }
154
155        /**
156         * Constructs a <code>SimpleQuantityFormat</code> using the default pattern. For
157         * full coverage, use the factory methods.
158         */
159        protected SimpleQuantityFormat() {
160                this("n u");
161        }
162
163        @Override
164        public Appendable format(Quantity<?> quantity, Appendable dest) throws IOException {
165                final Unit unit = quantity.getUnit();
166        /*
167                if (unit instanceof MixedUnit) {
168            if (quantity instanceof MixedQuantity) {
169                final MixedQuantity<?> compQuant = (MixedQuantity<?>) quantity;
170                final MixedUnit<?> compUnit = (MixedUnit<?>) unit;
171                final Number[] values = compQuant.getValues();
172                if (values.length == compUnit.getUnits().size()) {
173                    final StringBuffer sb = new StringBuffer(); // we use StringBuffer here because of java.text.Format compatibility
174                    for (int i = 0; i < values.length; i++) {
175                       sb.append(SimpleQuantityFormat.getInstance().format(
176                               Quantities.getQuantity(values[i], compUnit.getUnits().get(i), compQuant.getScale())));
177                       if (i < values.length-1) {
178                           sb.append(delimiter);
179                       }
180                    }
181                    return sb;
182                } else {
183                    throw new IllegalArgumentException(String.format("%s values don't match %s in mixed unit", values.length, compUnit.getUnits().size()));
184                }
185            } else {
186                throw new MeasurementException("The quantity is not a mixed quantity");
187            }
188        } else { */
189                dest.append(quantity.getValue().toString());
190                if (quantity.getUnit().equals(AbstractUnit.ONE))
191                        return dest;
192                dest.append(delimiter);
193                return SimpleUnitFormat.getInstance().format(unit, dest);
194        //}
195        }
196        
197        @SuppressWarnings("unchecked")
198        @Override
199        public Quantity<?> parse(CharSequence csq, ParsePosition cursor) throws MeasurementParseException {
200            
201            final NumberFormat numberFormat = NumberFormat.getInstance();
202            final SimpleUnitFormat simpleUnitFormat = SimpleUnitFormat.getInstance();
203            
204        if (mixDelimiter != null && !mixDelimiter.equals(delimiter)) {
205            return parseCompoundAsLeading(csq.toString(), numberFormat, simpleUnitFormat, delimiter, mixDelimiter, cursor.getIndex());
206        } else if (mixDelimiter != null && mixDelimiter.equals(delimiter)) {
207            return parseCompoundAsLeading(csq.toString(), numberFormat, simpleUnitFormat, delimiter, cursor.getIndex());
208        }
209        
210        final RationalNumberScanner scanner = new RationalNumberScanner(csq, cursor, null /*TODO should'nt this be numberFormat as well*/);
211        final Number number = scanner.getNumber();
212                
213                Unit unit = simpleUnitFormat.parse(csq, cursor);
214                return Quantities.getQuantity(number, unit);
215        }
216
217        @Override
218        protected Quantity<?> parse(CharSequence csq, int index) throws MeasurementParseException {
219                return parse(csq, new ParsePosition(index));
220        }
221
222        @Override
223        public Quantity<?> parse(CharSequence csq) throws MeasurementParseException {
224                return parse(csq, new ParsePosition(0));
225        }
226
227        /**
228         * Returns the quantity format for the default locale. The default format
229         * assumes the quantity is composed of a decimal number and a {@link Unit}
230         * separated by whitespace(s).
231         *
232         * @return a default <code>SimpleQuantityFormat</code> instance.
233         */
234        public static SimpleQuantityFormat getInstance() {
235                return DEFAULT;
236        }
237        
238        /**
239         * Returns a <code>SimpleQuantityFormat</code> using the given pattern.
240         * <p>
241         * 
242         * @param pattern
243         *            the pattern describing the quantity and unit format
244         *
245         * @return <code>SimpleQuantityFormat.getInstance(a pattern)</code>
246         */
247        public static SimpleQuantityFormat getInstance(String pattern) {
248                return new SimpleQuantityFormat(pattern);
249        }
250
251        @Override
252        public String toString() {
253            return getClass().getSimpleName();
254        }
255          
256        public String getPattern() {
257                return pattern;
258        }
259        
260    @Override
261    protected StringBuffer formatCompound(CompoundQuantity<?> comp, StringBuffer dest) {
262        final StringBuffer sb = new StringBuffer();
263        int i = 0;
264        for (Quantity<?> q : comp.getQuantities()) {
265            sb.append(format(q));
266            if (i < comp.getQuantities().size() - 1 ) {
267                sb.append((mixDelimiter != null ? mixDelimiter : DEFAULT_DELIMITER)); // we need null for parsing but not
268            }
269            i++;
270        }
271        return sb;
272    }
273}