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