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