001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2023, 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.CommonFormatter.parseMixedAsLeading; 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.MixedQuantity; 045import tech.units.indriya.quantity.Quantities; 046 047/** 048 * A simple implementation of QuantityFormat 049 * 050 * <br> 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 * @version 2.1, June 5, 2023 100 * @since 2.0 101 */ 102@SuppressWarnings("rawtypes") 103public class SimpleQuantityFormat extends AbstractQuantityFormat { 104 /** 105 * Holds the default format instance. 106 */ 107 private static final SimpleQuantityFormat DEFAULT = new SimpleQuantityFormat(); 108 109 private static final String NUM_PART = "n"; 110 private static final String UNIT_PART = "u"; 111 private static final String RADIX = "~"; 112 113 /** 114 * The pattern string of this formatter. This is always a non-localized pattern. 115 * May not be null. See class documentation for details. 116 * 117 * @serial 118 */ 119 private final String pattern; 120 121 private String delimiter; 122 123 private String mixDelimiter; 124 125 /** 126 * 127 */ 128 private static final long serialVersionUID = 2758248665095734058L; 129 130 /** 131 * Constructs a <code>SimpleQuantityFormat</code> using the given pattern. 132 * <p> 133 * 134 * @param pattern 135 * the pattern describing the quantity and unit format 136 * @exception NullPointerException 137 * if the given pattern is null 138 * @exception IllegalArgumentException 139 * if the given pattern is invalid 140 */ 141 public SimpleQuantityFormat(String pattern) { 142 this.pattern = pattern; 143 if (pattern != null && !pattern.isEmpty()) { 144 if (pattern.contains(RADIX)) { 145 final String singlePattern = pattern.substring(0, pattern.indexOf(RADIX)); 146 mixDelimiter = pattern.substring(pattern.indexOf(RADIX) + 1); 147 delimiter = singlePattern.substring(pattern.indexOf(NUM_PART)+1, pattern.indexOf(UNIT_PART)); 148 } else { 149 delimiter = pattern.substring(pattern.indexOf(NUM_PART)+1, pattern.indexOf(UNIT_PART)); 150 } 151 } 152 } 153 154 /** 155 * Constructs a <code>SimpleQuantityFormat</code> using the default pattern. For 156 * full coverage, use the factory methods. 157 */ 158 protected SimpleQuantityFormat() { 159 this("n u"); 160 } 161 162 @Override 163 public Appendable format(Quantity<?> quantity, Appendable dest) throws IOException { 164 final Unit unit = quantity.getUnit(); 165 /* 166 if (unit instanceof MixedUnit) { 167 if (quantity instanceof MixedQuantity) { 168 final MixedQuantity<?> compQuant = (MixedQuantity<?>) quantity; 169 final MixedUnit<?> compUnit = (MixedUnit<?>) unit; 170 final Number[] values = compQuant.getValues(); 171 if (values.length == compUnit.getUnits().size()) { 172 final StringBuffer sb = new StringBuffer(); // we use StringBuffer here because of java.text.Format compatibility 173 for (int i = 0; i < values.length; i++) { 174 sb.append(SimpleQuantityFormat.getInstance().format( 175 Quantities.getQuantity(values[i], compUnit.getUnits().get(i), compQuant.getScale()))); 176 if (i < values.length-1) { 177 sb.append(delimiter); 178 } 179 } 180 return sb; 181 } else { 182 throw new IllegalArgumentException(String.format("%s values don't match %s in mixed unit", values.length, compUnit.getUnits().size())); 183 } 184 } else { 185 throw new MeasurementException("The quantity is not a mixed quantity"); 186 } 187 } else { */ 188 dest.append(quantity.getValue().toString()); 189 if (quantity.getUnit().equals(AbstractUnit.ONE)) 190 return dest; 191 dest.append(delimiter); 192 return SimpleUnitFormat.getInstance().format(unit, dest); 193 //} 194 } 195 196 @SuppressWarnings("unchecked") 197 @Override 198 public Quantity<?> parse(CharSequence csq, ParsePosition cursor) throws MeasurementParseException { 199 200 final NumberFormat numberFormat = NumberFormat.getInstance(); 201 final SimpleUnitFormat simpleUnitFormat = SimpleUnitFormat.getInstance(); 202 203 if (mixDelimiter != null && !mixDelimiter.equals(delimiter)) { 204 return parseMixedAsLeading(csq.toString(), numberFormat, simpleUnitFormat, delimiter, mixDelimiter, cursor.getIndex()); 205 } else if (mixDelimiter != null && mixDelimiter.equals(delimiter)) { 206 return parseMixedAsLeading(csq.toString(), numberFormat, simpleUnitFormat, delimiter, cursor.getIndex()); 207 } 208 209 final RationalNumberScanner scanner = new RationalNumberScanner(csq, cursor, null /*TODO should'nt this be numberFormat as well*/); 210 final Number number = scanner.getNumber(); 211 212 Unit unit = simpleUnitFormat.parse(csq, cursor); 213 return Quantities.getQuantity(number, unit); 214 } 215 216 @Override 217 protected Quantity<?> parse(CharSequence csq, int index) throws MeasurementParseException { 218 return parse(csq, new ParsePosition(index)); 219 } 220 221 @Override 222 public Quantity<?> parse(CharSequence csq) throws MeasurementParseException { 223 return parse(csq, new ParsePosition(0)); 224 } 225 226 /** 227 * Returns the quantity format for the default locale. The default format 228 * assumes the quantity is composed of a decimal number and a {@link Unit} 229 * separated by whitespace(s). 230 * 231 * @return a default <code>SimpleQuantityFormat</code> instance. 232 */ 233 public static SimpleQuantityFormat getInstance() { 234 return DEFAULT; 235 } 236 237 /** 238 * Returns a <code>SimpleQuantityFormat</code> using the given pattern. 239 * <p> 240 * 241 * @param pattern 242 * the pattern describing the quantity and unit format 243 * 244 * @return <code>SimpleQuantityFormat.getInstance(a pattern)</code> 245 */ 246 public static SimpleQuantityFormat getInstance(String pattern) { 247 return new SimpleQuantityFormat(pattern); 248 } 249 250 @Override 251 public String toString() { 252 return getClass().getSimpleName(); 253 } 254 255 public String getPattern() { 256 return pattern; 257 } 258 259 @Override 260 protected StringBuffer formatMixed(MixedQuantity<?> mixed, StringBuffer dest) { 261 final StringBuffer sb = new StringBuffer(); 262 int i = 0; 263 for (Quantity<?> q : mixed.getQuantities()) { 264 sb.append(format(q)); 265 if (i < mixed.getQuantities().size() - 1 ) { 266 sb.append((mixDelimiter != null ? mixDelimiter : DEFAULT_DELIMITER)); // we need null for parsing but not 267 } 268 i++; 269 } 270 return sb; 271 } 272}