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}