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>&lt;sign&gt;</td>
077 * <td>:=</td>
078 * <td>"+" | "-"</td>
079 * </tr>
080 * <tr valign="top">
081 * <td>&lt;digit&gt;</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>&lt;superscript_digit&gt;</td>
087 * <td>:=</td>
088 * <td>"⁰" | "¹" | "²" | "³" | "⁴" | "⁵" | "⁶" | "⁷" | "⁸" | "⁹"</td>
089 * </tr>
090 * <tr valign="top">
091 * <td>&lt;integer&gt;</td>
092 * <td>:=</td>
093 * <td>(&lt;digit&gt;)+</td>
094 * </tr>
095 * <tr * valign="top">
096 * <td>&lt;number&gt;</td>
097 * <td>:=</td>
098 * <td>(&lt;sign&gt;)? (&lt;digit&gt;)* (".")? (&lt;digit&gt;)+ (("e" | "E") (&lt;sign&gt;)? (&lt;digit&gt;)+)?</td>
099 * </tr>
100 * <tr valign="top">
101 * <td>&lt;exponent&gt;</td>
102 * <td>:=</td>
103 * <td>( "^" ( &lt;sign&gt; )? &lt;integer&gt; ) <br>
104 * | ( "^(" (&lt;sign&gt;)? &lt;integer&gt; ( "/" (&lt;sign&gt;)? &lt;integer&gt; )? ")" ) <br>
105 * | ( &lt;superscript_digit&gt; )+</td>
106 * </tr>
107 * <tr valign="top">
108 * <td>&lt;initial_char&gt;</td>
109 * <td>:=</td>
110 * <td>? Any Unicode character excluding the following: ASCII control & whitespace (&#92;u0000 - &#92;u0020), decimal digits '0'-'9', '('
111 * (&#92;u0028), ')' (&#92;u0029), '*' (&#92;u002A), '+' (&#92;u002B), '-' (&#92;u002D), '.' (&#92;u002E), '/' (&#92;u005C), ':' (&#92;u003A), '^'
112 * (&#92;u005E), '²' (&#92;u00B2), '³' (&#92;u00B3), '·' (&#92;u00B7), '¹' (&#92;u00B9), '⁰' (&#92;u2070), '⁴' (&#92;u2074), '⁵' (&#92;u2075), '⁶'
113 * (&#92;u2076), '⁷' (&#92;u2077), '⁸' (&#92;u2078), '⁹' (&#92;u2079) ?</td>
114 * </tr>
115 * <tr valign="top">
116 * <td>&lt;unit_identifier&gt;</td>
117 * <td>:=</td>
118 * <td>&lt;initial_char&gt; ( &lt;initial_char&gt; | &lt;digit&gt; )*</td>
119 * </tr>
120 * <tr>
121 * <th colspan="3" align="left">Non-Terminals:</th>
122 * </tr>
123 * <tr * valign="top">
124 * <td>&lt;unit_expr&gt;</td>
125 * <td>:=</td>
126 * <td>&lt;compound_expr&gt;</td>
127 * </tr>
128 * <tr valign="top">
129 * <td>&lt;compound_expr&gt;</td>
130 * <td>:=</td>
131 * <td>&lt;add_expr&gt; ( ":" &lt;add_expr&gt; )*</td>
132 * </tr>
133 * <tr valign="top">
134 * <td>&lt;add_expr&gt;</td>
135 * <td>:=</td>
136 * <td>( &lt;number&gt; &lt;sign&gt; )? &lt;mul_expr&gt; ( &lt;sign&gt; &lt;number&gt; )?</td>
137 * </tr>
138 * <tr valign="top">
139 * <td>&lt;mul_expr&gt;</td>
140 * <td>:=</td>
141 * <td>&lt;exponent_expr&gt; ( ( ( "*" | "·" ) &lt;exponent_expr&gt; ) | ( "/" &lt;exponent_expr&gt; ) )*</td>
142 * </tr>
143 * <tr valign="top">
144 * <td>&lt;exponent_expr&gt;</td>
145 * <td>:=</td>
146 * <td>( &lt;atomic_expr&gt; ( &lt;exponent&gt; )? ) <br>
147 * | (&lt;integer&gt; "^" &lt;atomic_expr&gt;) <br>
148 * | ( ( "log" ( &lt;integer&gt; )? ) | "ln" ) "(" &lt;add_expr&gt; ")" )</td>
149 * </tr>
150 * <tr valign="top">
151 * <td>&lt;atomic_expr&gt;</td>
152 * <td>:=</td>
153 * <td>&lt;number&gt; <br>
154 * | &lt;unit_identifier&gt; <br>
155 * | ( "(" &lt;add_expr&gt; ")" )</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}