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}