001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2018, 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 static tech.units.indriya.format.FormatBehavior.LOCALE_NEUTRAL;
033
034import java.io.IOException;
035import java.math.BigDecimal;
036import java.text.NumberFormat;
037import java.text.ParsePosition;
038
039import javax.measure.Quantity;
040import javax.measure.Unit;
041import javax.measure.format.MeasurementParseException;
042import javax.measure.format.UnitFormat;
043
044import tech.units.indriya.AbstractUnit;
045import tech.units.indriya.ComparableQuantity;
046import tech.units.indriya.quantity.Quantities;
047
048/**
049 * An implementation of {@link javax.measure.format.QuantityFormat
050 * QuantityFormat} combining {@linkplain NumberFormat} and {@link UnitFormat}.
051 * 
052 * @version 1.2, $Date: 2018-07-12 $
053 * @since 1.0
054 */
055@SuppressWarnings({ "rawtypes", "unchecked" })
056public class NumberSpaceQuantityFormat extends AbstractQuantityFormat {
057        /**
058         * The default separator.
059         */
060        private static final String DEFAULT_SEPARATOR = " ";
061
062        /**
063         * Holds the default format instance.
064         */
065        private static final NumberSpaceQuantityFormat DEFAULT = new NumberSpaceQuantityFormat(NumberFormat.getInstance(),
066                        EBNFUnitFormat.getInstance());
067
068        /**
069         * Holds the localized format instance.
070         */
071        private static final NumberSpaceQuantityFormat LOCAL = new NumberSpaceQuantityFormat(NumberFormat.getInstance(),
072                        LocalUnitFormat.getInstance());
073
074        /**
075         * 
076         */
077        private static final long serialVersionUID = 3546952599885869402L;
078
079        private final NumberFormat numberFormat;
080
081        private final UnitFormat unitFormat;
082
083        NumberSpaceQuantityFormat(NumberFormat numberFormat, UnitFormat unitFormat) {
084                this.numberFormat = numberFormat;
085                this.unitFormat = unitFormat;
086        }
087
088        static int getFractionDigitsCount(double d) {
089                if (d >= 1) { // we only need the fraction digits
090                        d = d - (long) d;
091                }
092                if (d == 0) { // nothing to count
093                        return 0;
094                }
095                d *= 10; // shifts 1 digit to left
096                int count = 1;
097                while (d - (long) d != 0) { // keeps shifting until there are no more
098                        // fractions
099                        d *= 10;
100                        count++;
101                }
102                return count;
103        }
104
105        @Override
106        public Appendable format(Quantity<?> quantity, Appendable dest) throws IOException {
107                int fract = 0;
108                if (quantity != null && quantity.getValue() != null) {
109                        fract = getFractionDigitsCount(quantity.getValue().doubleValue());
110                }
111                if (fract > 1) {
112                        numberFormat.setMaximumFractionDigits(fract + 1);
113                }
114                dest.append(numberFormat.format(quantity.getValue()));
115                if (quantity.getUnit().equals(AbstractUnit.ONE))
116                        return dest;
117                dest.append(DEFAULT_SEPARATOR);
118                return unitFormat.format(quantity.getUnit(), dest);
119        }
120
121        @Override
122        public ComparableQuantity<?> parse(CharSequence csq, ParsePosition cursor)
123                        throws IllegalArgumentException, MeasurementParseException {
124                final String str = csq.toString();
125                final Number number = numberFormat.parse(str, cursor);
126                if (number == null)
127                        throw new IllegalArgumentException("Number cannot be parsed");
128                final String[] parts = str.split(DEFAULT_SEPARATOR);
129                if (parts.length < 2) {
130                        throw new IllegalArgumentException("No Unit found");
131                }
132                final Unit unit = unitFormat.parse(parts[1]);
133                return Quantities.getQuantity(number, unit);
134        }
135
136        @Override
137        ComparableQuantity<?> parse(CharSequence csq, int index)
138                        throws IllegalArgumentException, MeasurementParseException {
139                return parse(csq, new ParsePosition(index));
140        }
141
142        @Override
143        public ComparableQuantity<?> parse(CharSequence csq) throws IllegalArgumentException, MeasurementParseException {
144                return parse(csq, 0);
145        }
146
147        /**
148         * Returns the culture invariant format based upon {@link BigDecimal} canonical
149         * format and the {@link UnitFormat#getStandardInstance() standard} unit format.
150         * This format <b>is not</b> locale-sensitive and can be used for unambiguous
151         * electronic communication of quantities together with their units without loss
152         * of information. For example: <code>"1.23456789 kg.m/s2"</code> returns
153         * <code>Quantities.getQuantity(new BigDecimal("1.23456789"), AbstractUnit.parse("kg.m/s2")));</code>
154         *
155         * @param style
156         *            the format style to apply.
157         * @return the desired format.
158         */
159        public static NumberSpaceQuantityFormat getInstance(FormatBehavior style) {
160                switch (style) {
161                case LOCALE_NEUTRAL:
162                        return DEFAULT;
163                case LOCALE_SENSITIVE:
164                        return LOCAL;
165                default:
166                        return DEFAULT;
167                }
168        }
169
170        /**
171         * Returns the default format.
172         * 
173         * @return the desired format.
174         */
175        public static NumberSpaceQuantityFormat getInstance() {
176                return getInstance(LOCALE_NEUTRAL);
177        }
178
179        /**
180         * Returns the quantity format using the specified number format and unit format
181         * (the number and unit are separated by one space).
182         *
183         * @param numberFormat
184         *            the number format.
185         * @param unitFormat
186         *            the unit format.
187         * @return the corresponding format.
188         */
189        public static NumberSpaceQuantityFormat getInstance(NumberFormat numberFormat, UnitFormat unitFormat) {
190                return new NumberSpaceQuantityFormat(numberFormat, unitFormat);
191        }
192}