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.ConverterFormatter.formatConverterLocal; 033import static tech.units.indriya.format.FormatConstants.*; 034 035import javax.measure.Quantity; 036import javax.measure.Unit; 037import javax.measure.UnitConverter; 038import javax.measure.format.MeasurementParseException; 039import tech.units.indriya.AbstractUnit; 040import tech.units.indriya.internal.format.UnitFormatParser; 041import tech.units.indriya.unit.AlternateUnit; 042import tech.units.indriya.unit.AnnotatedUnit; 043import tech.units.indriya.unit.BaseUnit; 044import tech.units.indriya.unit.TransformedUnit; 045 046import static tech.units.indriya.unit.Units.CUBIC_METRE; 047import static tech.units.indriya.unit.Units.GRAM; 048import static tech.units.indriya.unit.Units.KILOGRAM; 049import static tech.units.indriya.unit.Units.LITRE; 050 051import java.io.IOException; 052import java.io.StringReader; 053import java.text.ParsePosition; 054import java.util.Locale; 055import java.util.Map; 056import java.util.ResourceBundle; 057 058/** 059 * <p> 060 * This class represents the local sensitive format. 061 * </p> 062 * 063 * <h3>Here is the grammar for CommonUnits in Extended Backus-Naur Form (EBNF)</h3> 064 * <p> 065 * Note that the grammar has been left-factored to be suitable for use by a top-down parser generator such as <a 066 * href="https://javacc.dev.java.net/">JavaCC</a> 067 * </p> 068 * <table width="90%" * align="center"> 069 * <tr> 070 * <th colspan="3" align="left">Lexical Entities:</th> 071 * </tr> 072 * <tr valign="top"> 073 * <td><sign></td> 074 * <td>:=</td> 075 * <td>"+" | "-"</td> 076 * </tr> 077 * <tr valign="top"> 078 * <td><digit></td> 079 * <td>:=</td> 080 * <td>"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"</td> 081 * </tr> 082 * <tr valign="top"> 083 * <td><superscript_digit></td> 084 * <td>:=</td> 085 * <td>"⁰" | "¹" | "²" | "³" | "⁴" | "⁵" | "⁶" | "⁷" | "⁸" | "⁹"</td> 086 * </tr> 087 * <tr valign="top"> 088 * <td><integer></td> 089 * <td>:=</td> 090 * <td>(<digit>)+</td> 091 * </tr> 092 * <tr * valign="top"> 093 * <td><number></td> 094 * <td>:=</td> 095 * <td>(<sign>)? (<digit>)* (".")? (<digit>)+ (("e" | "E") (<sign>)? (<digit>)+)?</td> 096 * </tr> 097 * <tr valign="top"> 098 * <td><exponent></td> 099 * <td>:=</td> 100 * <td>( "^" ( <sign> )? <integer> ) <br> 101 * | ( "^(" (<sign>)? <integer> ( "/" (<sign>)? <integer> )? ")" ) <br> 102 * | ( <superscript_digit> )+</td> 103 * </tr> 104 * <tr valign="top"> 105 * <td><initial_char></td> 106 * <td>:=</td> 107 * <td>? Any Unicode character excluding the following: ASCII control & whitespace (\u0000 - \u0020), decimal digits '0'-'9', '(' 108 * (\u0028), ')' (\u0029), '*' (\u002A), '+' (\u002B), '-' (\u002D), '.' (\u002E), '/' (\u005C), ':' (\u003A), '^' 109 * (\u005E), '²' (\u00B2), '³' (\u00B3), '·' (\u00B7), '¹' (\u00B9), '⁰' (\u2070), '⁴' (\u2074), '⁵' (\u2075), '⁶' 110 * (\u2076), '⁷' (\u2077), '⁸' (\u2078), '⁹' (\u2079) ?</td> 111 * </tr> 112 * <tr valign="top"> 113 * <td><unit_identifier></td> 114 * <td>:=</td> 115 * <td><initial_char> ( <initial_char> | <digit> )*</td> 116 * </tr> 117 * <tr> 118 * <th colspan="3" align="left">Non-Terminals:</th> 119 * </tr> 120 * <tr * valign="top"> 121 * <td><unit_expr></td> 122 * <td>:=</td> 123 * <td><mix_expr></td> 124 * </tr> 125 * <tr valign="top"> 126 * <td><mix_expr></td> 127 * <td>:=</td> 128 * <td><add_expr> ( ":" <add_expr> )*</td> 129 * </tr> 130 * <tr valign="top"> 131 * <td><add_expr></td> 132 * <td>:=</td> 133 * <td>( <number> <sign> )? <mul_expr> ( <sign> <number> )?</td> 134 * </tr> 135 * <tr valign="top"> 136 * <td><mul_expr></td> 137 * <td>:=</td> 138 * <td><exponent_expr> ( ( ( "*" | "·" ) <exponent_expr> ) | ( "/" <exponent_expr> ) )*</td> 139 * </tr> 140 * <tr valign="top"> 141 * <td><exponent_expr></td> 142 * <td>:=</td> 143 * <td>( <atomic_expr> ( <exponent> )? ) <br> 144 * | (<integer> "^" <atomic_expr>) <br> 145 * | ( ( "log" ( <integer> )? ) | "ln" ) "(" <add_expr> ")" )</td> 146 * </tr> 147 * <tr valign="top"> 148 * <td><atomic_expr></td> 149 * <td>:=</td> 150 * <td><number> <br> 151 * | <unit_identifier> <br> 152 * | ( "(" <add_expr> ")" )</td> 153 * </tr> 154 * </table> 155 * 156 * @author <a href="mailto:eric-r@northwestern.edu">Eric Russell</a> 157 * @author <a href="mailto:werner@units.tech">Werner Keil</a> 158 * @version 1.2.2, March 11, 2019 159 * @since 1.0 160 */ 161public class LocalUnitFormat extends AbstractUnitFormat { 162 163 // //////////////////////////////////////////////////// 164 // Class variables // 165 // //////////////////////////////////////////////////// 166 /** 167 * DefaultQuantityFactory locale instance. If the default locale is changed after the class is initialized, this instance will no longer be used. 168 */ 169 private static final LocalUnitFormat DEFAULT_INSTANCE = new LocalUnitFormat(SymbolMap.of(ResourceBundle.getBundle(LocalUnitFormat.class 170 .getPackage().getName() + ".messages"))); 171 172 // ///////////////// 173 // Class methods // 174 // ///////////////// 175 /** 176 * Returns the instance for the current default locale (non-ascii characters are allowed) 177 */ 178 public static LocalUnitFormat getInstance() { 179 return DEFAULT_INSTANCE; 180 } 181 182 /** 183 * Returns an instance for the given locale. 184 * 185 * @param locale 186 */ 187 public static LocalUnitFormat getInstance(Locale locale) { 188 return new LocalUnitFormat(SymbolMap.of(ResourceBundle.getBundle(LocalUnitFormat.class.getPackage().getName() + ".messages", locale))); 189 } 190 191 /** Returns an instance for the given symbol map. */ 192 public static LocalUnitFormat getInstance(SymbolMap symbols) { 193 return new LocalUnitFormat(symbols); 194 } 195 196 // ////////////////////// 197 // Instance variables // 198 // ////////////////////// 199 /** 200 * The symbol map used by this instance to map between {@link Unit Unit}s and <code>String</code>s, etc... 201 */ 202 private final transient SymbolMap symbolMap; 203 204 // //////////////// 205 // Constructors // 206 // //////////////// 207 /** 208 * Base constructor. 209 * 210 * @param symbols 211 * the symbol mapping. 212 */ 213 private LocalUnitFormat(SymbolMap symbols) { 214 symbolMap = symbols; 215 } 216 217 //////////////////////// 218 // Instance methods // 219 //////////////////////// 220 /** 221 * Get the symbol map used by this instance to map between {@link AbstractUnit Unit}s and <code>String</code>s, etc... 222 * 223 * @return SymbolMap the current symbol map 224 */ 225 @Override 226 protected SymbolMap getSymbols() { 227 return symbolMap; 228 } 229 230 @Override 231 public String toString() { 232 return getClass().getSimpleName(); 233 } 234 235 // ////////////// 236 // Formatting // 237 // ////////////// 238 @Override 239 public Appendable format(Unit<?> unit, Appendable appendable) throws IOException { 240 if (!(unit instanceof AbstractUnit)) { 241 return appendable.append(unit.toString()); // Unknown unit (use 242 // intrinsic toString() 243 // method) 244 } 245 formatInternal(unit, appendable); 246 return appendable; 247 } 248 249 public boolean isLocaleSensitive() { 250 return true; 251 } 252 253 protected Unit<?> parse(CharSequence csq, int index) throws MeasurementParseException { 254 return parse(csq, new ParsePosition(index)); 255 } 256 257 public Unit<?> parse(CharSequence csq, ParsePosition cursor) throws MeasurementParseException { 258 // Parsing reads the whole character sequence from the parse position. 259 int start = cursor.getIndex(); 260 int end = csq.length(); 261 if (end <= start) { 262 return AbstractUnit.ONE; 263 } 264 String source = csq.subSequence(start, end).toString().trim(); 265 if (source.length() == 0) { 266 return AbstractUnit.ONE; 267 } 268 try { 269 UnitFormatParser parser = new UnitFormatParser(symbolMap, new StringReader(source)); 270 Unit<?> result = parser.parseUnit(); 271 cursor.setIndex(end); 272 return result; 273 } catch (TokenException e) { 274 if (e.currentToken != null) { 275 cursor.setErrorIndex(start + e.currentToken.endColumn); 276 } else { 277 cursor.setErrorIndex(start); 278 } 279 throw new IllegalArgumentException(e); // TODO should we throw 280 // ParserException here, 281 // too? 282 } catch (TokenMgrError e) { 283 cursor.setErrorIndex(start); 284 throw new MeasurementParseException(e); 285 } 286 } 287 288 @Override 289 public Unit<? extends Quantity<?>> parse(CharSequence csq) throws MeasurementParseException { 290 return parse(csq, new ParsePosition(0)); 291 } 292 293 /** 294 * Format the given unit to the given StringBuilder, then return the operator precedence of the outermost operator in the unit expression that was 295 * formatted. See {@link ConverterFormat} for the constants that define the various precedence values. 296 * 297 * @param unit 298 * the unit to be formatted 299 * @param buffer 300 * the <code>StringBuilder</code> to be written to 301 * @return the operator precedence of the outermost operator in the unit expression that was output 302 */ 303 @SuppressWarnings({ "rawtypes", "unchecked" }) 304 private int formatInternal(Unit<?> unit, Appendable buffer) throws IOException { 305 if (unit instanceof AnnotatedUnit<?>) { 306 unit = ((AnnotatedUnit<?>) unit).getActualUnit(); 307 // } else if (unit instanceof ProductUnit<?>) { 308 // ProductUnit<?> p = (ProductUnit<?>)unit; 309 } 310 // TODO is the behavior similar to EBNFUnitFormat for AnnotatedUnit? 311 String symbol = symbolMap.getSymbol((AbstractUnit<?>) unit); 312 if (symbol != null) { 313 buffer.append(symbol); 314 return NOOP_PRECEDENCE; 315 } else if (unit.getBaseUnits() != null) { 316 Map<Unit<?>, Integer> productUnits = (Map<Unit<?>, Integer>) unit.getBaseUnits(); 317 int negativeExponentCount = 0; 318 // Write positive exponents first... 319 boolean start = true; 320 for (Map.Entry<Unit<?>, Integer> e : productUnits.entrySet()) { 321 int pow = e.getValue(); 322 if (pow >= 0) { 323 formatExponent(e.getKey(), pow, 1, !start, buffer); 324 start = false; 325 } else { 326 negativeExponentCount += 1; 327 } 328 } 329 // ..then write negative exponents. 330 if (negativeExponentCount > 0) { 331 if (start) { 332 buffer.append('1'); 333 } 334 buffer.append('/'); 335 if (negativeExponentCount > 1) { 336 buffer.append('('); 337 } 338 start = true; 339 for (Map.Entry<Unit<?>, Integer> e : productUnits.entrySet()) { 340 int pow = e.getValue(); 341 if (pow < 0) { 342 formatExponent(e.getKey(), -pow, 1, !start, buffer); 343 start = false; 344 } 345 } 346 if (negativeExponentCount > 1) { 347 buffer.append(')'); 348 } 349 } 350 return PRODUCT_PRECEDENCE; 351 } else if (unit instanceof BaseUnit<?>) { 352 buffer.append(((BaseUnit<?>) unit).getSymbol()); 353 return NOOP_PRECEDENCE; 354 } else if (unit instanceof AlternateUnit<?>) { // unit.getSymbol() != 355 // null) { // Alternate 356 // unit. 357 buffer.append(unit.getSymbol()); 358 return NOOP_PRECEDENCE; 359 // TODO add case for MixedUnit 360 } else { // A transformed unit or new unit type! 361 UnitConverter converter = null; 362 boolean printSeparator = false; 363 StringBuilder temp = new StringBuilder(); 364 int unitPrecedence = NOOP_PRECEDENCE; 365 Unit<?> parentUnit = unit.getSystemUnit(); 366 converter = ((AbstractUnit<?>) unit).getSystemConverter(); 367 if (KILOGRAM.equals(parentUnit)) { 368 // More special-case hackery to work around gram/kilogram 369 // incosistency 370 if (unit.equals(GRAM)) { 371 buffer.append(symbolMap.getSymbol(GRAM)); 372 return NOOP_PRECEDENCE; 373 } 374 parentUnit = GRAM; 375 if (unit instanceof TransformedUnit<?>) { 376 converter = ((TransformedUnit<?>) unit).getConverter(); 377 } else { 378 converter = unit.getConverterTo((Unit) GRAM); 379 } 380 } else if (CUBIC_METRE.equals(parentUnit)) { 381 if (converter != null) { 382 parentUnit = LITRE; 383 } 384 } 385 386 if (unit instanceof TransformedUnit) { 387 TransformedUnit<?> transUnit = (TransformedUnit<?>) unit; 388 if (parentUnit == null) 389 parentUnit = transUnit.getParentUnit(); 390 // String x = parentUnit.toString(); 391 converter = transUnit.getConverter(); 392 } 393 394 unitPrecedence = formatInternal(parentUnit, temp); 395 printSeparator = !parentUnit.equals(AbstractUnit.ONE); 396 int result = formatConverterLocal(converter, printSeparator, unitPrecedence, temp, symbolMap); 397 buffer.append(temp); 398 return result; 399 } 400 } 401 402 /** 403 * Format the given unit raised to the given fractional power to the given <code>StringBuffer</code>. 404 * 405 * @param unit 406 * Unit the unit to be formatted 407 * @param pow 408 * int the numerator of the fractional power 409 * @param root 410 * int the denominator of the fractional power 411 * @param continued 412 * boolean <code>true</code> if the converter expression should begin with an operator, otherwise <code>false</code>. This will always be 413 * true unless the unit being modified is equal to Unit.ONE. 414 * @param buffer 415 * StringBuffer the buffer to append to. No assumptions should be made about its content. 416 */ 417 private void formatExponent(Unit<?> unit, int pow, int root, boolean continued, Appendable buffer) throws IOException { 418 if (continued) { 419 buffer.append(MIDDLE_DOT); 420 } 421 StringBuffer temp = new StringBuffer(); 422 int unitPrecedence = formatInternal(unit, temp); 423 if (unitPrecedence < PRODUCT_PRECEDENCE) { 424 temp.insert(0, '('); 425 temp.append(')'); 426 } 427 buffer.append(temp); 428 if ((root == 1) && (pow == 1)) { 429 // do nothing 430 } else if ((root == 1) && (pow > 1)) { 431 String powStr = Integer.toString(pow); 432 for (int i = 0; i < powStr.length(); i += 1) { 433 char c = powStr.charAt(i); 434 switch (c) { 435 case '0': 436 buffer.append('\u2070'); 437 break; 438 case '1': 439 buffer.append('\u00b9'); 440 break; 441 case '2': 442 buffer.append('\u00b2'); 443 break; 444 case '3': 445 buffer.append('\u00b3'); 446 break; 447 case '4': 448 buffer.append('\u2074'); 449 break; 450 case '5': 451 buffer.append('\u2075'); 452 break; 453 case '6': 454 buffer.append('\u2076'); 455 break; 456 case '7': 457 buffer.append('\u2077'); 458 break; 459 case '8': 460 buffer.append('\u2078'); 461 break; 462 case '9': 463 buffer.append('\u2079'); 464 break; 465 } 466 } 467 } else if (root == 1) { 468 buffer.append("^"); 469 buffer.append(String.valueOf(pow)); 470 } else { 471 buffer.append("^("); 472 buffer.append(String.valueOf(pow)); 473 buffer.append('/'); 474 buffer.append(String.valueOf(root)); 475 buffer.append(')'); 476 } 477 } 478}