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