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 javax.measure.Prefix; 033import javax.measure.Quantity; 034import javax.measure.Unit; 035import javax.measure.UnitConverter; 036import javax.measure.format.MeasurementParseException; 037import tech.units.indriya.AbstractUnit; 038import tech.units.indriya.function.AddConverter; 039import tech.units.indriya.function.MultiplyConverter; 040import tech.units.indriya.function.RationalConverter; 041import tech.units.indriya.internal.format.LocalUnitFormatParser; 042import tech.units.indriya.internal.format.TokenException; 043import tech.units.indriya.internal.format.TokenMgrError; 044import tech.units.indriya.unit.AlternateUnit; 045import tech.units.indriya.unit.AnnotatedUnit; 046import tech.units.indriya.unit.BaseUnit; 047import tech.units.indriya.unit.TransformedUnit; 048 049import static tech.units.indriya.unit.Units.CUBIC_METRE; 050import static tech.units.indriya.unit.Units.GRAM; 051import static tech.units.indriya.unit.Units.KILOGRAM; 052import static tech.units.indriya.unit.Units.LITRE; 053 054import java.io.IOException; 055import java.io.StringReader; 056import java.math.BigInteger; 057import java.text.ParsePosition; 058import java.util.Locale; 059import java.util.Map; 060import java.util.ResourceBundle; 061 062/** 063 * <p> 064 * This class represents the local sensitive format. 065 * </p> 066 * 067 * <h3>Here is the grammar for CommonUnits in Extended Backus-Naur Form (EBNF)</h3> 068 * <p> 069 * Note that the grammar has been left-factored to be suitable for use by a top-down parser generator such as <a 070 * href="https://javacc.dev.java.net/">JavaCC</a> 071 * </p> 072 * <table width="90%" * align="center"> 073 * <tr> 074 * <th colspan="3" align="left">Lexical Entities:</th> 075 * </tr> 076 * <tr valign="top"> 077 * <td><sign></td> 078 * <td>:=</td> 079 * <td>"+" | "-"</td> 080 * </tr> 081 * <tr valign="top"> 082 * <td><digit></td> 083 * <td>:=</td> 084 * <td>"0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"</td> 085 * </tr> 086 * <tr valign="top"> 087 * <td><superscript_digit></td> 088 * <td>:=</td> 089 * <td>"⁰" | "¹" | "²" | "³" | "⁴" | "⁵" | "⁶" | "⁷" | "⁸" | "⁹"</td> 090 * </tr> 091 * <tr valign="top"> 092 * <td><integer></td> 093 * <td>:=</td> 094 * <td>(<digit>)+</td> 095 * </tr> 096 * <tr * valign="top"> 097 * <td><number></td> 098 * <td>:=</td> 099 * <td>(<sign>)? (<digit>)* (".")? (<digit>)+ (("e" | "E") (<sign>)? (<digit>)+)?</td> 100 * </tr> 101 * <tr valign="top"> 102 * <td><exponent></td> 103 * <td>:=</td> 104 * <td>( "^" ( <sign> )? <integer> ) <br> 105 * | ( "^(" (<sign>)? <integer> ( "/" (<sign>)? <integer> )? ")" ) <br> 106 * | ( <superscript_digit> )+</td> 107 * </tr> 108 * <tr valign="top"> 109 * <td><initial_char></td> 110 * <td>:=</td> 111 * <td>? Any Unicode character excluding the following: ASCII control & whitespace (\u0000 - \u0020), decimal digits '0'-'9', '(' 112 * (\u0028), ')' (\u0029), '*' (\u002A), '+' (\u002B), '-' (\u002D), '.' (\u002E), '/' (\u005C), ':' (\u003A), '^' 113 * (\u005E), '²' (\u00B2), '³' (\u00B3), '·' (\u00B7), '¹' (\u00B9), '⁰' (\u2070), '⁴' (\u2074), '⁵' (\u2075), '⁶' 114 * (\u2076), '⁷' (\u2077), '⁸' (\u2078), '⁹' (\u2079) ?</td> 115 * </tr> 116 * <tr valign="top"> 117 * <td><unit_identifier></td> 118 * <td>:=</td> 119 * <td><initial_char> ( <initial_char> | <digit> )*</td> 120 * </tr> 121 * <tr> 122 * <th colspan="3" align="left">Non-Terminals:</th> 123 * </tr> 124 * <tr * valign="top"> 125 * <td><unit_expr></td> 126 * <td>:=</td> 127 * <td><compound_expr></td> 128 * </tr> 129 * <tr valign="top"> 130 * <td><compound_expr></td> 131 * <td>:=</td> 132 * <td><add_expr> ( ":" <add_expr> )*</td> 133 * </tr> 134 * <tr valign="top"> 135 * <td><add_expr></td> 136 * <td>:=</td> 137 * <td>( <number> <sign> )? <mul_expr> ( <sign> <number> )?</td> 138 * </tr> 139 * <tr valign="top"> 140 * <td><mul_expr></td> 141 * <td>:=</td> 142 * <td><exponent_expr> ( ( ( "*" | "·" ) <exponent_expr> ) | ( "/" <exponent_expr> ) )*</td> 143 * </tr> 144 * <tr valign="top"> 145 * <td><exponent_expr></td> 146 * <td>:=</td> 147 * <td>( <atomic_expr> ( <exponent> )? ) <br> 148 * | (<integer> "^" <atomic_expr>) <br> 149 * | ( ( "log" ( <integer> )? ) | "ln" ) "(" <add_expr> ")" )</td> 150 * </tr> 151 * <tr valign="top"> 152 * <td><atomic_expr></td> 153 * <td>:=</td> 154 * <td><number> <br> 155 * | <unit_identifier> <br> 156 * | ( "(" <add_expr> ")" )</td> 157 * </tr> 158 * </table> 159 * 160 * @author <a href="mailto:eric-r@northwestern.edu">Eric Russell</a> 161 * @author <a href="mailto:units@catmedia.us">Werner Keil</a> 162 * @version 1.0.2, April 30, 2017 163 * @since 1.0 164 */ 165public class LocalUnitFormat extends AbstractUnitFormat { 166 167 // //////////////////////////////////////////////////// 168 // Class variables // 169 // //////////////////////////////////////////////////// 170 /** 171 * DefaultQuantityFactory locale instance. If the default locale is changed after the class is initialized, this instance will no longer be used. 172 */ 173 private static final LocalUnitFormat DEFAULT_INSTANCE = new LocalUnitFormat(SymbolMap.of(ResourceBundle.getBundle(LocalUnitFormat.class 174 .getPackage().getName() + ".messages"))); 175 /** 176 * Multiplicand character 177 */ 178 private static final char MIDDLE_DOT = '\u00b7'; 179 /** 180 * Operator precedence for the addition and subtraction operations 181 */ 182 private static final int ADDITION_PRECEDENCE = 0; 183 /** 184 * Operator precedence for the multiplication and division operations 185 */ 186 private static final int PRODUCT_PRECEDENCE = ADDITION_PRECEDENCE + 2; 187 /** 188 * Operator precedence for the exponentiation and logarithm operations 189 */ 190 private static final int EXPONENT_PRECEDENCE = PRODUCT_PRECEDENCE + 2; 191 /** 192 * Operator precedence for a unit identifier containing no mathematical operations (i.e., consisting exclusively of an identifier and possibly a 193 * prefix). Defined to be <code>Integer.MAX_VALUE</code> so that no operator can have a higher precedence. 194 */ 195 private static final int NOOP_PRECEDENCE = Integer.MAX_VALUE; 196 197 // ///////////////// 198 // Class methods // 199 // ///////////////// 200 /** 201 * Returns the instance for the current default locale (non-ascii characters are allowed) 202 */ 203 public static LocalUnitFormat getInstance() { 204 return DEFAULT_INSTANCE; 205 } 206 207 /** 208 * Returns an instance for the given locale. 209 * 210 * @param locale 211 */ 212 public static LocalUnitFormat getInstance(Locale locale) { 213 return new LocalUnitFormat(SymbolMap.of(ResourceBundle.getBundle(LocalUnitFormat.class.getPackage().getName() + ".messages", locale))); 214 } 215 216 /** Returns an instance for the given symbol map. */ 217 public static LocalUnitFormat getInstance(SymbolMap symbols) { 218 return new LocalUnitFormat(symbols); 219 } 220 221 // ////////////////////// 222 // Instance variables // 223 // ////////////////////// 224 /** 225 * The symbol map used by this instance to map between {@link Unit Unit}s and <code>String</code>s, etc... 226 */ 227 private final transient SymbolMap symbolMap; 228 229 // //////////////// 230 // Constructors // 231 // //////////////// 232 /** 233 * Base constructor. 234 * 235 * @param symbols 236 * the symbol mapping. 237 */ 238 private LocalUnitFormat(SymbolMap symbols) { 239 symbolMap = symbols; 240 } 241 242 // ////////////////////// 243 // Instance methods // 244 // ////////////////////// 245 /** 246 * Get the symbol map used by this instance to map between {@link AbstractUnit Unit}s and <code>String</code>s, etc... 247 * 248 * @return SymbolMap the current symbol map 249 */ 250 @Override 251 protected SymbolMap getSymbols() { 252 return symbolMap; 253 } 254 255 // ////////////// 256 // Formatting // 257 // ////////////// 258 @Override 259 public Appendable format(Unit<?> unit, Appendable appendable) throws IOException { 260 if (!(unit instanceof AbstractUnit)) { 261 return appendable.append(unit.toString()); // Unknown unit (use 262 // intrinsic toString() 263 // method) 264 } 265 formatInternal(unit, appendable); 266 return appendable; 267 } 268 269 public boolean isLocaleSensitive() { 270 return true; 271 } 272 273 protected Unit<?> parse(CharSequence csq, int index) throws MeasurementParseException { 274 return parse(csq, new ParsePosition(index)); 275 } 276 277 public Unit<?> parse(CharSequence csq, ParsePosition cursor) throws MeasurementParseException { 278 // Parsing reads the whole character sequence from the parse position. 279 int start = cursor.getIndex(); 280 int end = csq.length(); 281 if (end <= start) { 282 return AbstractUnit.ONE; 283 } 284 String source = csq.subSequence(start, end).toString().trim(); 285 if (source.length() == 0) { 286 return AbstractUnit.ONE; 287 } 288 try { 289 LocalUnitFormatParser parser = new LocalUnitFormatParser(symbolMap, new StringReader(source)); 290 Unit<?> result = parser.parseUnit(); 291 cursor.setIndex(end); 292 return result; 293 } catch (TokenException e) { 294 if (e.currentToken != null) { 295 cursor.setErrorIndex(start + e.currentToken.endColumn); 296 } else { 297 cursor.setErrorIndex(start); 298 } 299 throw new IllegalArgumentException(e); // TODO should we throw 300 // ParserException here, 301 // too? 302 } catch (TokenMgrError e) { 303 cursor.setErrorIndex(start); 304 throw new MeasurementParseException(e); 305 } 306 } 307 308 @Override 309 public Unit<? extends Quantity<?>> parse(CharSequence csq) throws MeasurementParseException { 310 return parse(csq, new ParsePosition(0)); 311 } 312 313 /** 314 * Format the given unit to the given StringBuilder, then return the operator precedence of the outermost operator in the unit expression that was 315 * formatted. See {@link ConverterFormat} for the constants that define the various precedence values. 316 * 317 * @param unit 318 * the unit to be formatted 319 * @param buffer 320 * the <code>StringBuilder</code> to be written to 321 * @return the operator precedence of the outermost operator in the unit expression that was output 322 */ 323 /* 324 * private int formatInternal(Unit<?> unit, Appendable buffer) throws 325 * IOException { if (unit instanceof AnnotatedUnit) { unit = 326 * ((AnnotatedUnit) unit).getActualUnit(); } String symbol = 327 * symbolMap.getSymbol((AbstractUnit<?>) unit); if (symbol != null) { 328 * buffer.append(symbol); return NOOP_PRECEDENCE; } else if 329 * (unit.getBaseUnits() != null) { Map<? extends Unit, Integer> productUnits 330 * = unit.getBaseUnits(); int negativeExponentCount = 0; // Write positive 331 * exponents first... boolean start = true; for (Unit u : 332 * productUnits.keySet()) { int pow = productUnits.get(u); if (pow >= 0) { 333 * formatExponent(u, pow, 1, !start, buffer); start = false; } else { 334 * negativeExponentCount += 1; } } // ..then write negative exponents. if 335 * (negativeExponentCount > 0) { if (start) { buffer.append('1'); } 336 * buffer.append('/'); if (negativeExponentCount > 1) { buffer.append('('); 337 * } start = true; for (Unit u : productUnits.keySet()) { int pow = 338 * productUnits.get(u); if (pow < 0) { formatExponent(u, -pow, 1, !start, 339 * buffer); start = false; } } if (negativeExponentCount > 1) { 340 * buffer.append(')'); } } return PRODUCT_PRECEDENCE; } else if 341 * ((!((AbstractUnit)unit).isSystemUnit()) || unit.equals(Units.KILOGRAM)) { 342 * UnitConverter converter = null; boolean printSeparator = false; 343 * StringBuffer temp = new StringBuffer(); int unitPrecedence = 344 * NOOP_PRECEDENCE; if (unit.equals(Units.KILOGRAM)) { // A special case 345 * because KILOGRAM is a BaseUnit instead of // a transformed unit, even 346 * though it has a prefix. converter = MetricPrefix.KILO.getConverter(); 347 * unitPrecedence = formatInternal(Units.GRAM, temp); printSeparator = true; 348 * } else { Unit parentUnit = unit.getSystemUnit(); converter = 349 * unit.getConverterTo(parentUnit); if (parentUnit.equals(Units.KILOGRAM)) { 350 * // More special-case hackery to work around gram/kilogram // incosistency 351 * parentUnit = Units.GRAM; converter = 352 * converter.concatenate(MetricPrefix.KILO.getConverter()); } unitPrecedence 353 * = formatInternal(parentUnit, temp); printSeparator = 354 * !parentUnit.equals(Units.ONE); } int result = formatConverter(converter, 355 * printSeparator, unitPrecedence, temp); buffer.append(temp); return 356 * result; } else if (unit.getSymbol() != null) { 357 * buffer.append(unit.getSymbol()); return NOOP_PRECEDENCE; } else { throw 358 * new IllegalArgumentException( 359 * "Cannot format the given Object as a Unit (unsupported unit type " + 360 * unit.getClass().getName() + ")"); } } 361 */ 362 @SuppressWarnings({ "rawtypes", "unchecked" }) 363 private int formatInternal(Unit<?> unit, Appendable buffer) throws IOException { 364 if (unit instanceof AnnotatedUnit<?>) { 365 unit = ((AnnotatedUnit<?>) unit).getActualUnit(); 366 // } else if (unit instanceof ProductUnit<?>) { 367 // ProductUnit<?> p = (ProductUnit<?>)unit; 368 } 369 // TODO is the behavior similar to EBNFUnitFormat for AnnotatedUnit? 370 String symbol = symbolMap.getSymbol((AbstractUnit<?>) unit); 371 if (symbol != null) { 372 buffer.append(symbol); 373 return NOOP_PRECEDENCE; 374 } else if (unit.getBaseUnits() != null) { 375 Map<Unit<?>, Integer> productUnits = (Map<Unit<?>, Integer>) unit.getBaseUnits(); 376 int negativeExponentCount = 0; 377 // Write positive exponents first... 378 boolean start = true; 379 for (Map.Entry<Unit<?>, Integer> e : productUnits.entrySet()) { 380 int pow = e.getValue(); 381 if (pow >= 0) { 382 formatExponent(e.getKey(), pow, 1, !start, buffer); 383 start = false; 384 } else { 385 negativeExponentCount += 1; 386 } 387 } 388 // ..then write negative exponents. 389 if (negativeExponentCount > 0) { 390 if (start) { 391 buffer.append('1'); 392 } 393 buffer.append('/'); 394 if (negativeExponentCount > 1) { 395 buffer.append('('); 396 } 397 start = true; 398 for (Map.Entry<Unit<?>, Integer> e : productUnits.entrySet()) { 399 int pow = e.getValue(); 400 if (pow < 0) { 401 formatExponent(e.getKey(), -pow, 1, !start, buffer); 402 start = false; 403 } 404 } 405 if (negativeExponentCount > 1) { 406 buffer.append(')'); 407 } 408 } 409 return PRODUCT_PRECEDENCE; 410 } else if (unit instanceof BaseUnit<?>) { 411 buffer.append(((BaseUnit<?>) unit).getSymbol()); 412 return NOOP_PRECEDENCE; 413 } else if (unit instanceof AlternateUnit<?>) { // unit.getSymbol() != 414 // null) { // Alternate 415 // unit. 416 buffer.append(unit.getSymbol()); 417 return NOOP_PRECEDENCE; 418 } else { // A transformed unit or new unit type! 419 UnitConverter converter = null; 420 boolean printSeparator = false; 421 StringBuilder temp = new StringBuilder(); 422 int unitPrecedence = NOOP_PRECEDENCE; 423 Unit<?> parentUnit = unit.getSystemUnit(); 424 converter = ((AbstractUnit<?>) unit).getSystemConverter(); 425 if (KILOGRAM.equals(parentUnit)) { 426 // More special-case hackery to work around gram/kilogram 427 // incosistency 428 if (unit.equals(GRAM)) { 429 buffer.append(symbolMap.getSymbol(GRAM)); 430 return NOOP_PRECEDENCE; 431 } 432 parentUnit = GRAM; 433 if (unit instanceof TransformedUnit<?>) { 434 converter = ((TransformedUnit<?>) unit).getConverter(); 435 } else { 436 converter = unit.getConverterTo((Unit) GRAM); 437 } 438 } else if (CUBIC_METRE.equals(parentUnit)) { 439 if (converter != null) { 440 parentUnit = LITRE; 441 } 442 } 443 444 if (unit instanceof TransformedUnit) { 445 TransformedUnit<?> transUnit = (TransformedUnit<?>) unit; 446 if (parentUnit == null) 447 parentUnit = transUnit.getParentUnit(); 448 // String x = parentUnit.toString(); 449 converter = transUnit.getConverter(); 450 } 451 452 unitPrecedence = formatInternal(parentUnit, temp); 453 printSeparator = !parentUnit.equals(AbstractUnit.ONE); 454 int result = formatConverter(converter, printSeparator, unitPrecedence, temp); 455 buffer.append(temp); 456 return result; 457 } 458 } 459 460 /** 461 * Format the given unit raised to the given fractional power to the given <code>StringBuffer</code>. 462 * 463 * @param unit 464 * Unit the unit to be formatted 465 * @param pow 466 * int the numerator of the fractional power 467 * @param root 468 * int the denominator of the fractional power 469 * @param continued 470 * boolean <code>true</code> if the converter expression should begin with an operator, otherwise <code>false</code>. This will always be 471 * true unless the unit being modified is equal to Unit.ONE. 472 * @param buffer 473 * StringBuffer the buffer to append to. No assumptions should be made about its content. 474 */ 475 private void formatExponent(Unit<?> unit, int pow, int root, boolean continued, Appendable buffer) throws IOException { 476 if (continued) { 477 buffer.append(MIDDLE_DOT); 478 } 479 StringBuffer temp = new StringBuffer(); 480 int unitPrecedence = formatInternal(unit, temp); 481 if (unitPrecedence < PRODUCT_PRECEDENCE) { 482 temp.insert(0, '('); 483 temp.append(')'); 484 } 485 buffer.append(temp); 486 if ((root == 1) && (pow == 1)) { 487 // do nothing 488 } else if ((root == 1) && (pow > 1)) { 489 String powStr = Integer.toString(pow); 490 for (int i = 0; i < powStr.length(); i += 1) { 491 char c = powStr.charAt(i); 492 switch (c) { 493 case '0': 494 buffer.append('\u2070'); 495 break; 496 case '1': 497 buffer.append('\u00b9'); 498 break; 499 case '2': 500 buffer.append('\u00b2'); 501 break; 502 case '3': 503 buffer.append('\u00b3'); 504 break; 505 case '4': 506 buffer.append('\u2074'); 507 break; 508 case '5': 509 buffer.append('\u2075'); 510 break; 511 case '6': 512 buffer.append('\u2076'); 513 break; 514 case '7': 515 buffer.append('\u2077'); 516 break; 517 case '8': 518 buffer.append('\u2078'); 519 break; 520 case '9': 521 buffer.append('\u2079'); 522 break; 523 } 524 } 525 } else if (root == 1) { 526 buffer.append("^"); 527 buffer.append(String.valueOf(pow)); 528 } else { 529 buffer.append("^("); 530 buffer.append(String.valueOf(pow)); 531 buffer.append('/'); 532 buffer.append(String.valueOf(root)); 533 buffer.append(')'); 534 } 535 } 536 537 /** 538 * Formats the given converter to the given StringBuffer and returns the operator precedence of the converter's mathematical operation. This is the 539 * default implementation, which supports all built-in UnitConverter implementations. Note that it recursively calls itself in the case of a 540 * {@link javax.measure.converter.UnitConverter.Compound Compound} converter. 541 * 542 * @param converter 543 * the converter to be formatted 544 * @param continued 545 * <code>true</code> if the converter expression should begin with an operator, otherwise <code>false</code>. 546 * @param unitPrecedence 547 * the operator precedence of the operation expressed by the unit being modified by the given converter. 548 * @param buffer 549 * the <code>StringBuffer</code> to append to. 550 * @return the operator precedence of the given UnitConverter 551 */ 552 private int formatConverter(UnitConverter converter, boolean continued, int unitPrecedence, StringBuilder buffer) { 553 Prefix prefix = symbolMap.getPrefix(converter); 554 if ((prefix != null) && (unitPrecedence == NOOP_PRECEDENCE)) { 555 buffer.insert(0, symbolMap.getSymbol(prefix)); 556 return NOOP_PRECEDENCE; 557 } else if (converter instanceof AddConverter) { 558 if (unitPrecedence < ADDITION_PRECEDENCE) { 559 buffer.insert(0, '('); 560 buffer.append(')'); 561 } 562 double offset = ((AddConverter) converter).getOffset(); 563 if (offset < 0) { 564 buffer.append("-"); 565 offset = -offset; 566 } else if (continued) { 567 buffer.append("+"); 568 } 569 long lOffset = (long) offset; 570 if (lOffset == offset) { 571 buffer.append(lOffset); 572 } else { 573 buffer.append(offset); 574 } 575 return ADDITION_PRECEDENCE; 576 } else if (converter instanceof MultiplyConverter) { 577 if (unitPrecedence < PRODUCT_PRECEDENCE) { 578 buffer.insert(0, '('); 579 buffer.append(')'); 580 } 581 if (continued) { 582 buffer.append(MIDDLE_DOT); 583 } 584 double factor = ((MultiplyConverter) converter).getFactor(); 585 long lFactor = (long) factor; 586 if (lFactor == factor) { 587 buffer.append(lFactor); 588 } else { 589 buffer.append(factor); 590 } 591 return PRODUCT_PRECEDENCE; 592 } else if (converter instanceof RationalConverter) { 593 if (unitPrecedence < PRODUCT_PRECEDENCE) { 594 buffer.insert(0, '('); 595 buffer.append(')'); 596 } 597 RationalConverter rationalConverter = (RationalConverter) converter; 598 if (!rationalConverter.getDividend().equals(BigInteger.ONE)) { 599 if (continued) { 600 buffer.append(MIDDLE_DOT); 601 } 602 buffer.append(rationalConverter.getDividend()); 603 } 604 if (!rationalConverter.getDivisor().equals(BigInteger.ONE)) { 605 buffer.append('/'); 606 buffer.append(rationalConverter.getDivisor()); 607 } 608 return PRODUCT_PRECEDENCE; 609 } else { // All other converter type (e.g. exponential) we use the 610 // string representation. 611 buffer.insert(0, converter.toString() + "("); 612 buffer.append(")"); 613 return EXPONENT_PRECEDENCE; 614 } 615 } 616}