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