001/*
002 * Units of Measurement Reference Implementation
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.units.ri.format;
031
032import static tec.units.ri.unit.MetricPrefix.*;
033import static tec.units.ri.unit.Units.LITRE;
034
035import java.io.IOException;
036import java.lang.CharSequence;
037import java.util.HashMap;
038import java.util.Map;
039
040import tec.units.ri.AbstractUnit;
041import tec.units.ri.function.AddConverter;
042import tec.units.ri.function.MultiplyConverter;
043import tec.units.ri.function.RationalConverter;
044import tec.units.ri.internal.format.l10n.FieldPosition;
045import tec.units.ri.internal.format.l10n.ParsePosition;
046import tec.units.ri.unit.AlternateUnit;
047import tec.units.ri.unit.BaseUnit;
048import tec.units.ri.unit.ProductUnit;
049import tec.units.ri.unit.TransformedUnit;
050import tec.units.ri.unit.Units;
051import tec.units.ri.unit.MetricPrefix;
052
053import javax.measure.Unit;
054import javax.measure.UnitConverter;
055import javax.measure.Quantity;
056import javax.measure.format.ParserException;
057import javax.measure.format.UnitFormat;
058
059/**
060 * <p>
061 * This class implements the {@link UnitFormat} interface for formatting and parsing {@link Unit units}.
062 * </p>
063 * 
064 * <p>
065 * For all SI units, the 20 SI prefixes used to form decimal multiples and sub-multiples of SI units are recognized. {@link Units} are directly
066 * recognized. For example:<br>
067 * <code>
068 *        AbstractUnit.parse("m°C").equals(MetricPrefix.MILLI(Units.CELSIUS))
069 *        AbstractUnit.parse("kW").equals(MetricPrefix.KILO(Units.WATT))
070 *        AbstractUnit.parse("ft").equals(Units.METRE.multiply(0.3048))</code>
071 * </p>
072 *
073 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
074 * @author <a href="mailto:units@catmedia.us">Werner Keil</a>
075 * @author Eric Russell
076 * @version 1.0.3, June 7, 2017
077 * @since 1.0
078 */
079public abstract class SimpleUnitFormat extends AbstractUnitFormat {
080  /**
081     * 
082     */
083  // private static final long serialVersionUID = 4149424034841739785L;
084
085  /**
086   * Flavor of this format
087   *
088   * @author Werner
089   *
090   */
091  public enum Flavor {
092    Default, ASCII
093  }
094
095  /**
096   * Holds the standard unit format.
097   */
098  private static final DefaultFormat DEFAULT = new DefaultFormat();
099
100  /**
101   * Holds the ASCIIFormat unit format.
102   */
103  private static final ASCIIFormat ASCII = new ASCIIFormat();
104
105  /**
106   * Returns the unit format for the default locale (format used by {@link AbstractUnit#parse(CharSequence) AbstractUnit.parse(CharSequence)} and
107   * {@link Unit#toString() Unit.toString()}).
108   *
109   * @return the default unit format (locale sensitive).
110   */
111  public static SimpleUnitFormat getInstance() {
112    return getInstance(Flavor.Default);
113  }
114
115  /**
116   * Returns the {@link SimpleUnitFormat} in the desired {@link Flavor}
117   *
118   * @return the instance for the given {@link Flavor}.
119   */
120  public static SimpleUnitFormat getInstance(Flavor flavor) {
121    switch (flavor) {
122      case ASCII:
123        return SimpleUnitFormat.ASCII;
124      default:
125        return DEFAULT;
126    }
127  }
128
129  /**
130   * Base constructor.
131   */
132  protected SimpleUnitFormat() {
133  }
134
135  /**
136   * Formats the specified unit.
137   *
138   * @param unit
139   *          the unit to format.
140   * @param appendable
141   *          the appendable destination.
142   * @throws IOException
143   *           if an error occurs.
144   */
145  public abstract Appendable format(Unit<?> unit, Appendable appendable) throws IOException;
146
147  /**
148   * Parses a sequence of character to produce a unit or a rational product of unit.
149   *
150   * @param csq
151   *          the <code>CharSequence</code> to parse.
152   * @param pos
153   *          an object holding the parsing index and error position.
154   * @return an {@link Unit} parsed from the character sequence.
155   * @throws IllegalArgumentException
156   *           if the character sequence contains an illegal syntax.
157   */
158  @SuppressWarnings("rawtypes")
159  public abstract Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) throws ParserException;
160
161  /**
162   * Parses a sequence of character to produce a single unit.
163   *
164   * @param csq
165   *          the <code>CharSequence</code> to parse.
166   * @param pos
167   *          an object holding the parsing index and error position.
168   * @return an {@link Unit} parsed from the character sequence.
169   * @throws IllegalArgumentException
170   *           if the character sequence does not contain a valid unit identifier.
171   */
172  @SuppressWarnings("rawtypes")
173  public abstract Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) throws ParserException;
174
175  /**
176   * Attaches a system-wide label to the specified unit. For example: <code> SimpleUnitFormat.getInstance().label(DAY.multiply(365), "year");
177   * SimpleUnitFormat.getInstance().label(METER.multiply(0.3048), "ft"); </code> If the specified label is already associated to an unit the previous
178   * association is discarded or ignored.
179   *
180   * @param unit
181   *          the unit being labeled.
182   * @param label
183   *          the new label for this unit.
184   * @throws IllegalArgumentException
185   *           if the label is not a {@link SimpleUnitFormat#isValidIdentifier(String)} valid identifier.
186   */
187  public abstract void label(Unit<?> unit, String label);
188
189  public boolean isLocaleSensitive() {
190    return false;
191  }
192
193  /**
194   * Attaches a system-wide alias to this unit. Multiple aliases may be attached to the same unit. Aliases are used during parsing to recognize
195   * different variants of the same unit. For example: <code> SimpleUnitFormat.getInstance().alias(METER.multiply(0.3048), "foot");
196   * SimpleUnitFormat.getInstance().alias(METER.multiply(0.3048), "feet"); SimpleUnitFormat.getInstance().alias(METER, "meter");
197   * SimpleUnitFormat.getInstance().alias(METER, "metre"); </code> If the specified label is already associated to an unit the previous association is
198   * discarded or ignored.
199   *
200   * @param unit
201   *          the unit being aliased.
202   * @param alias
203   *          the alias attached to this unit.
204   * @throws IllegalArgumentException
205   *           if the label is not a {@link SimpleUnitFormat#isValidIdentifier(String)} valid identifier.
206   */
207  public abstract void alias(Unit<?> unit, String alias);
208
209  /**
210   * Indicates if the specified name can be used as unit identifier.
211   *
212   * @param name
213   *          the identifier to be tested.
214   * @return <code>true</code> if the name specified can be used as label or alias for this format;<code>false</code> otherwise.
215   */
216  public abstract boolean isValidIdentifier(String name);
217
218  /**
219   * Formats an unit and appends the resulting text to a given string buffer (implements <code>java.text.Format</code>).
220   *
221   * @param unit
222   *          the unit to format.
223   * @param toAppendTo
224   *          where the text is to be appended
225   * @param pos
226   *          the field position (not used).
227   * @return <code>toAppendTo</code>
228   */
229  public final StringBuffer format(Object unit, final StringBuffer toAppendTo, FieldPosition pos) {
230    try {
231      Object dest = toAppendTo;
232      if (dest instanceof Appendable) {
233        format((Unit<?>) unit, (Appendable) dest);
234      } else { // When retroweaver is used to produce 1.4 binaries.
235        format((Unit<?>) unit, new Appendable() {
236
237          public Appendable append(char arg0) throws IOException {
238            toAppendTo.append(arg0);
239            return null;
240          }
241
242          public Appendable append(CharSequence arg0) throws IOException {
243            toAppendTo.append(arg0);
244            return null;
245          }
246
247          public Appendable append(CharSequence arg0, int arg1, int arg2) throws IOException {
248            toAppendTo.append(arg0.subSequence(arg1, arg2));
249            return null;
250          }
251        });
252      }
253      return toAppendTo;
254    } catch (IOException e) {
255      throw new Error(e); // Should never happen.
256    }
257  }
258
259  /**
260   * Parses the text from a string to produce an object (implements <code>java.text.Format</code>).
261   *
262   * @param source
263   *          the string source, part of which should be parsed.
264   * @param pos
265   *          the cursor position.
266   * @return the corresponding unit or <code>null</code> if the string cannot be parsed.
267   */
268  public final Unit<?> parseObject(String source, ParsePosition pos) throws ParserException {
269    // int start = pos.getIndex();
270    return parseProductUnit(source, pos);
271    /*
272     * } catch (ParserException e) { pos.setIndex(start);
273     * pos.setErrorIndex(e.getPosition()); return null; }
274     */
275  }
276
277  /**
278   * This class represents an exponent with both a power (numerator) and a root (denominator).
279   */
280  private static class Exponent {
281    public final int pow;
282    public final int root;
283
284    public Exponent(int pow, int root) {
285      this.pow = pow;
286      this.root = root;
287    }
288  }
289
290  /**
291   * This class represents the standard format.
292   */
293  protected static class DefaultFormat extends SimpleUnitFormat {
294
295    /**
296     * Holds the name to unit mapping.
297     */
298    final HashMap<String, Unit<?>> _nameToUnit = new HashMap<>();
299
300    /**
301     * Holds the unit to name mapping.
302     */
303    final HashMap<Unit<?>, String> _unitToName = new HashMap<>();
304
305    @Override
306    public void label(Unit<?> unit, String label) {
307      if (!isValidIdentifier(label))
308        throw new IllegalArgumentException("Label: " + label + " is not a valid identifier.");
309      synchronized (this) {
310        _nameToUnit.put(label, unit);
311        _unitToName.put(unit, label);
312      }
313    }
314
315    @Override
316    public void alias(Unit<?> unit, String alias) {
317      if (!isValidIdentifier(alias))
318        throw new IllegalArgumentException("Alias: " + alias + " is not a valid identifier.");
319      synchronized (this) {
320        _nameToUnit.put(alias, unit);
321      }
322    }
323
324    @Override
325    public boolean isValidIdentifier(String name) {
326      if ((name == null) || (name.length() == 0))
327        return false;
328      /*
329       * for (int i = 0; i < name.length(); i++) { if
330       * (!isUnitIdentifierPart(name.charAt(i))) return false; }
331       */
332      return isUnitIdentifierPart(name.charAt(0));
333    }
334
335    static boolean isUnitIdentifierPart(char ch) {
336      return Character.isLetter(ch)
337          || (!Character.isWhitespace(ch) && !Character.isDigit(ch) && (ch != '\u00b7') && (ch != '*') && (ch != '/') && (ch != '(') && (ch != ')')
338              && (ch != '[') && (ch != ']') && (ch != '\u00b9') && (ch != '\u00b2') && (ch != '\u00b3') && (ch != '^') && (ch != '+') && (ch != '-'));
339    }
340
341    // Returns the name for the specified unit or null if product unit.
342    protected String nameFor(Unit<?> unit) {
343      // Searches label database.
344      String label = _unitToName.get(unit);
345      if (label != null)
346        return label;
347      if (unit instanceof BaseUnit)
348        return ((BaseUnit<?>) unit).getSymbol();
349      if (unit instanceof AlternateUnit)
350        return ((AlternateUnit<?>) unit).getSymbol();
351      if (unit instanceof TransformedUnit) {
352        TransformedUnit<?> tfmUnit = (TransformedUnit<?>) unit;
353        Unit<?> baseUnit = tfmUnit.getParentUnit();
354        UnitConverter cvtr = tfmUnit.getConverter(); // tfmUnit.getSystemConverter();
355        StringBuilder result = new StringBuilder();
356        String baseUnitName = baseUnit.toString();
357        String prefix = prefixFor(cvtr);
358        if ((baseUnitName.indexOf('\u00b7') >= 0) || (baseUnitName.indexOf('*') >= 0) || (baseUnitName.indexOf('/') >= 0)) {
359          // We could use parentheses whenever baseUnits is an
360          // instanceof ProductUnit, but most ProductUnits have
361          // aliases,
362          // so we'd end up with a lot of unnecessary parentheses.
363          result.append('(');
364          result.append(baseUnitName);
365          result.append(')');
366        } else {
367          result.append(baseUnitName);
368        }
369        if (prefix != null) {
370          result.insert(0, prefix);
371        } else {
372          if (cvtr instanceof AddConverter) {
373            result.append('+');
374            result.append(((AddConverter) cvtr).getOffset());
375          } else if (cvtr instanceof RationalConverter) {
376            double dividend = ((RationalConverter) cvtr).getDividend();
377            if (dividend != 1) {
378              result.append('*');
379              result.append(dividend);
380            }
381            double divisor = ((RationalConverter) cvtr).getDivisor();
382            if (divisor != 1) {
383              result.append('/');
384              result.append(divisor);
385            }
386          } else if (cvtr instanceof MultiplyConverter) {
387            result.append('*');
388            result.append(((MultiplyConverter) cvtr).getFactor());
389          } else { // Other converters.
390            return "[" + baseUnit + "?]";
391          }
392        }
393        return result.toString();
394      }
395      // Compound unit.
396      // if (unit instanceof CompoundUnit) {
397      // CompoundUnit<?> cpdUnit = (CompoundUnit<?>) unit;
398      // return nameFor(cpdUnit.getHigher()).toString() + ":"
399      // + nameFor(cpdUnit.getLower());
400      // }
401      return null; // Product unit.
402    }
403
404    // Returns the prefix for the specified unit converter.
405    protected String prefixFor(UnitConverter converter) {
406      for (int i = 0; i < CONVERTERS.length; i++) {
407        if (CONVERTERS[i].equals(converter)) {
408          return PREFIXES[i];
409        }
410      }
411      return null; // TODO or return blank?
412    }
413
414    // Returns the unit for the specified name.
415    protected Unit<?> unitFor(String name) {
416      Unit<?> unit = _nameToUnit.get(name);
417      if (unit != null)
418        return unit;
419      unit = SYMBOL_TO_UNIT.get(name);
420      return unit;
421    }
422
423    // //////////////////////////
424    // Parsing.
425    @SuppressWarnings({ "rawtypes", "unchecked" })
426    public Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) throws ParserException {
427      int startIndex = pos.getIndex();
428      String name = readIdentifier(csq, pos);
429      Unit unit = unitFor(name);
430      check(unit != null, name + " not recognized", csq, startIndex);
431      return unit;
432    }
433
434    @SuppressWarnings({ "rawtypes", "unchecked" })
435    @Override
436    public Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) throws ParserException {
437      Unit result = AbstractUnit.ONE;
438      int token = nextToken(csq, pos);
439      switch (token) {
440        case IDENTIFIER:
441          result = parseSingleUnit(csq, pos);
442          break;
443        case OPEN_PAREN:
444          pos.setIndex(pos.getIndex() + 1);
445          result = parseProductUnit(csq, pos);
446          token = nextToken(csq, pos);
447          check(token == CLOSE_PAREN, "')' expected", csq, pos.getIndex());
448          pos.setIndex(pos.getIndex() + 1);
449          break;
450      }
451      token = nextToken(csq, pos);
452      while (true) {
453        switch (token) {
454          case EXPONENT:
455            Exponent e = readExponent(csq, pos);
456            if (e.pow != 1) {
457              result = result.pow(e.pow);
458            }
459            if (e.root != 1) {
460              result = result.root(e.root);
461            }
462            break;
463          case MULTIPLY:
464            pos.setIndex(pos.getIndex() + 1);
465            token = nextToken(csq, pos);
466            if (token == INTEGER) {
467              long n = readLong(csq, pos);
468              if (n != 1) {
469                result = result.multiply(n);
470              }
471            } else if (token == FLOAT) {
472              double d = readDouble(csq, pos);
473              if (d != 1.0) {
474                result = result.multiply(d);
475              }
476            } else {
477              result = result.multiply(parseProductUnit(csq, pos));
478            }
479            break;
480          case DIVIDE:
481            pos.setIndex(pos.getIndex() + 1);
482            token = nextToken(csq, pos);
483            if (token == INTEGER) {
484              long n = readLong(csq, pos);
485              if (n != 1) {
486                result = result.divide(n);
487              }
488            } else if (token == FLOAT) {
489              double d = readDouble(csq, pos);
490              if (d != 1.0) {
491                result = result.divide(d);
492              }
493            } else {
494              result = result.divide(parseProductUnit(csq, pos));
495            }
496            break;
497          case PLUS:
498            pos.setIndex(pos.getIndex() + 1);
499            token = nextToken(csq, pos);
500            if (token == INTEGER) {
501              long n = readLong(csq, pos);
502              if (n != 1) {
503                result = result.shift(n);
504              }
505            } else if (token == FLOAT) {
506              double d = readDouble(csq, pos);
507              if (d != 1.0) {
508                result = result.shift(d);
509              }
510            } else {
511              throw new ParserException("not a number", pos.getIndex());
512            }
513            break;
514          case EOF:
515          case CLOSE_PAREN:
516            return result;
517          default:
518            throw new ParserException("unexpected token " + token, pos.getIndex());
519        }
520        token = nextToken(csq, pos);
521      }
522    }
523
524    private static final int EOF = 0;
525    private static final int IDENTIFIER = 1;
526    private static final int OPEN_PAREN = 2;
527    private static final int CLOSE_PAREN = 3;
528    private static final int EXPONENT = 4;
529    private static final int MULTIPLY = 5;
530    private static final int DIVIDE = 6;
531    private static final int PLUS = 7;
532    private static final int INTEGER = 8;
533    private static final int FLOAT = 9;
534
535    private int nextToken(CharSequence csq, ParsePosition pos) {
536      final int length = csq.length();
537      while (pos.getIndex() < length) {
538        char c = csq.charAt(pos.getIndex());
539        if (isUnitIdentifierPart(c)) {
540          return IDENTIFIER;
541        } else if (c == '(') {
542          return OPEN_PAREN;
543        } else if (c == ')') {
544          return CLOSE_PAREN;
545        } else if ((c == '^') || (c == '\u00b9') || (c == '\u00b2') || (c == '\u00b3')) {
546          return EXPONENT;
547        } else if (c == '*') {
548          char c2 = csq.charAt(pos.getIndex() + 1);
549          if (c2 == '*') {
550            return EXPONENT;
551          } else {
552            return MULTIPLY;
553          }
554        } else if (c == '\u00b7') {
555          return MULTIPLY;
556        } else if (c == '/') {
557          return DIVIDE;
558        } else if (c == '+') {
559          return PLUS;
560        } else if ((c == '-') || Character.isDigit(c)) {
561          int index = pos.getIndex() + 1;
562          while ((index < length) && (Character.isDigit(c) || (c == '-') || (c == '.') || (c == 'E'))) {
563            c = csq.charAt(index++);
564            if (c == '.') {
565              return FLOAT;
566            }
567          }
568          return INTEGER;
569        }
570        pos.setIndex(pos.getIndex() + 1);
571      }
572      return EOF;
573    }
574
575    private void check(boolean expr, String message, CharSequence csq, int index) throws ParserException {
576      if (!expr) {
577        throw new ParserException(message + " (in " + csq + " at index " + index + ")", index);
578      }
579    }
580
581    private Exponent readExponent(CharSequence csq, ParsePosition pos) {
582      char c = csq.charAt(pos.getIndex());
583      if (c == '^') {
584        pos.setIndex(pos.getIndex() + 1);
585      } else if (c == '*') {
586        pos.setIndex(pos.getIndex() + 2);
587      }
588      final int length = csq.length();
589      int pow = 0;
590      boolean isPowNegative = false;
591      int root = 0;
592      boolean isRootNegative = false;
593      boolean isRoot = false;
594      while (pos.getIndex() < length) {
595        c = csq.charAt(pos.getIndex());
596        if (c == '\u00b9') {
597          if (isRoot) {
598            root = root * 10 + 1;
599          } else {
600            pow = pow * 10 + 1;
601          }
602        } else if (c == '\u00b2') {
603          if (isRoot) {
604            root = root * 10 + 2;
605          } else {
606            pow = pow * 10 + 2;
607          }
608        } else if (c == '\u00b3') {
609          if (isRoot) {
610            root = root * 10 + 3;
611          } else {
612            pow = pow * 10 + 3;
613          }
614        } else if (c == '-') {
615          if (isRoot) {
616            isRootNegative = true;
617          } else {
618            isPowNegative = true;
619          }
620        } else if ((c >= '0') && (c <= '9')) {
621          if (isRoot) {
622            root = root * 10 + (c - '0');
623          } else {
624            pow = pow * 10 + (c - '0');
625          }
626        } else if (c == ':') {
627          isRoot = true;
628        } else {
629          break;
630        }
631        pos.setIndex(pos.getIndex() + 1);
632      }
633      if (pow == 0)
634        pow = 1;
635      if (root == 0)
636        root = 1;
637      return new Exponent(isPowNegative ? -pow : pow, isRootNegative ? -root : root);
638    }
639
640    private long readLong(CharSequence csq, ParsePosition pos) {
641      final int length = csq.length();
642      int result = 0;
643      boolean isNegative = false;
644      while (pos.getIndex() < length) {
645        char c = csq.charAt(pos.getIndex());
646        if (c == '-') {
647          isNegative = true;
648        } else if ((c >= '0') && (c <= '9')) {
649          result = result * 10 + (c - '0');
650        } else {
651          break;
652        }
653        pos.setIndex(pos.getIndex() + 1);
654      }
655      return isNegative ? -result : result;
656    }
657
658    private double readDouble(CharSequence csq, ParsePosition pos) {
659      final int length = csq.length();
660      int start = pos.getIndex();
661      int end = start + 1;
662      while (end < length) {
663        if ("0123456789+-.E".indexOf(csq.charAt(end)) < 0) {
664          break;
665        }
666        end += 1;
667      }
668      pos.setIndex(end + 1);
669      return Double.parseDouble(csq.subSequence(start, end).toString());
670    }
671
672    private String readIdentifier(CharSequence csq, ParsePosition pos) {
673      final int length = csq.length();
674      int start = pos.getIndex();
675      int i = start;
676      while ((++i < length) && isUnitIdentifierPart(csq.charAt(i))) {
677      }
678      pos.setIndex(i);
679      return csq.subSequence(start, i).toString();
680    }
681
682    // //////////////////////////
683    // Formatting.
684
685    @Override
686    public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
687      String name = nameFor(unit);
688      if (name != null) {
689        return appendable.append(name);
690      }
691      if (!(unit instanceof ProductUnit)) {
692        throw new IllegalArgumentException("Cannot format given Object as a Unit");
693      }
694
695      // Product unit.
696      ProductUnit<?> productUnit = (ProductUnit<?>) unit;
697      int invNbr = 0;
698
699      // Write positive exponents first.
700      boolean start = true;
701      for (int i = 0; i < productUnit.getUnitCount(); i++) {
702        int pow = productUnit.getUnitPow(i);
703        if (pow >= 0) {
704          if (!start) {
705            appendable.append('\u00b7'); // Separator.
706          }
707          name = nameFor(productUnit.getUnit(i));
708          int root = productUnit.getUnitRoot(i);
709          append(appendable, name, pow, root);
710          start = false;
711        } else {
712          invNbr++;
713        }
714      }
715
716      // Write negative exponents.
717      if (invNbr != 0) {
718        if (start) {
719          appendable.append('1'); // e.g. 1/s
720        }
721        appendable.append('/');
722        if (invNbr > 1) {
723          appendable.append('(');
724        }
725        start = true;
726        for (int i = 0; i < productUnit.getUnitCount(); i++) {
727          int pow = productUnit.getUnitPow(i);
728          if (pow < 0) {
729            name = nameFor(productUnit.getUnit(i));
730            int root = productUnit.getUnitRoot(i);
731            if (!start) {
732              appendable.append('\u00b7'); // Separator.
733            }
734            append(appendable, name, -pow, root);
735            start = false;
736          }
737        }
738        if (invNbr > 1) {
739          appendable.append(')');
740        }
741      }
742      return appendable;
743    }
744
745    private void append(Appendable appendable, CharSequence symbol, int pow, int root) throws IOException {
746      appendable.append(symbol);
747      if ((pow != 1) || (root != 1)) {
748        // Write exponent.
749        if ((pow == 2) && (root == 1)) {
750          appendable.append('\u00b2'); // Square
751        } else if ((pow == 3) && (root == 1)) {
752          appendable.append('\u00b3'); // Cubic
753        } else {
754          // Use general exponent form.
755          appendable.append('^');
756          appendable.append(String.valueOf(pow));
757          if (root != 1) {
758            appendable.append(':');
759            appendable.append(String.valueOf(root));
760          }
761        }
762      }
763    }
764
765    // private static final long serialVersionUID = 1L;
766
767    @Override
768    public Unit<?> parse(CharSequence csq) throws ParserException {
769      return parse(csq, 0);
770    }
771
772    @Override
773    protected Unit<?> parse(CharSequence csq, int index) throws ParserException {
774      return parseObject(csq.toString(), new ParsePosition(index));
775    }
776  }
777
778  /**
779   * This class represents the ASCII format.
780   */
781  protected final static class ASCIIFormat extends DefaultFormat {
782
783    @Override
784    protected String nameFor(Unit<?> unit) {
785      // First search if specific ASCII name should be used.
786      String name = _unitToName.get(unit);
787      if (name != null)
788        return name;
789      // Else returns default name.
790      return DEFAULT.nameFor(unit);
791    }
792
793    @Override
794    protected Unit<?> unitFor(String name) {
795      // First search if specific ASCII name.
796      Unit<?> unit = _nameToUnit.get(name);
797      if (unit != null)
798        return unit;
799      // Else returns default mapping.
800      return DEFAULT.unitFor(name);
801    }
802
803    @Override
804    public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
805      String name = nameFor(unit);
806      if (name != null)
807        return appendable.append(name);
808      if (!(unit instanceof ProductUnit))
809        throw new IllegalArgumentException("Cannot format given Object as a Unit");
810
811      ProductUnit<?> productUnit = (ProductUnit<?>) unit;
812      for (int i = 0; i < productUnit.getUnitCount(); i++) {
813        if (i != 0) {
814          appendable.append('*'); // Separator.
815        }
816        name = nameFor(productUnit.getUnit(i));
817        int pow = productUnit.getUnitPow(i);
818        int root = productUnit.getUnitRoot(i);
819        appendable.append(name);
820        if ((pow != 1) || (root != 1)) {
821          // Use general exponent form.
822          appendable.append('^');
823          appendable.append(String.valueOf(pow));
824          if (root != 1) {
825            appendable.append(':');
826            appendable.append(String.valueOf(root));
827          }
828        }
829      }
830      return appendable;
831    }
832
833    @Override
834    public boolean isValidIdentifier(String name) {
835      if ((name == null) || (name.length() == 0))
836        return false;
837      // label must not begin with a digit or mathematical operator
838      return isUnitIdentifierPart(name.charAt(0)) && isAllASCII(name);
839      /*
840       * for (int i = 0; i < name.length(); i++) { if
841       * (!isAsciiCharacter(name.charAt(i))) return false; } return true;
842       */
843    }
844  }
845
846  /**
847   * Holds the unique symbols collection (base units or alternate units).
848   */
849  private static final Map<String, Unit<?>> SYMBOL_TO_UNIT = new HashMap<>();
850
851  // //////////////////////////////////////////////////////////////////////////
852  // Initializes the standard unit database for SI units.
853
854  private static final Unit<?>[] SI_UNITS = { Units.AMPERE, Units.BECQUEREL, Units.CANDELA, Units.COULOMB, Units.FARAD, Units.GRAY, Units.HENRY,
855      Units.HERTZ, Units.JOULE, Units.KATAL, Units.KELVIN, Units.LUMEN, Units.LUX, Units.METRE, Units.MOLE, Units.NEWTON, Units.OHM, Units.PASCAL,
856      Units.RADIAN, Units.SECOND, Units.SIEMENS, Units.SIEVERT, Units.STERADIAN, Units.TESLA, Units.VOLT, Units.WATT, Units.WEBER };
857
858  private static final String[] PREFIXES = { YOTTA.getSymbol(), ZETTA.getSymbol(), EXA.getSymbol(), PETA.getSymbol(), TERA.getSymbol(),
859      GIGA.getSymbol(), MEGA.getSymbol(), KILO.getSymbol(), HECTO.getSymbol(), DEKA.getSymbol(), DECI.getSymbol(), CENTI.getSymbol(),
860      MILLI.getSymbol(), MICRO.getSymbol(), NANO.getSymbol(), PICO.getSymbol(), FEMTO.getSymbol(), ATTO.getSymbol(), ZEPTO.getSymbol(),
861      YOCTO.getSymbol() };
862
863  // TODO we could try retrieving this dynamically in a static {} method from
864  // MetricPrefix if symbols above are also aligned
865  private static final UnitConverter[] CONVERTERS = { YOTTA.getConverter(), ZETTA.getConverter(), EXA.getConverter(), PETA.getConverter(),
866      TERA.getConverter(), GIGA.getConverter(), MEGA.getConverter(), KILO.getConverter(), HECTO.getConverter(), DEKA.getConverter(),
867      DECI.getConverter(), CENTI.getConverter(), MILLI.getConverter(), MICRO.getConverter(), NANO.getConverter(), PICO.getConverter(),
868      FEMTO.getConverter(), ATTO.getConverter(), ZEPTO.getConverter(), YOCTO.getConverter() };
869
870  private static String asciiPrefix(String prefix) {
871    return prefix == "µ" ? "micro" : prefix;
872  }
873
874  // to check if a string only contains US-ASCII characters
875  //
876  protected static boolean isAllASCII(String input) {
877    boolean isASCII = true;
878    for (int i = 0; i < input.length(); i++) {
879      int c = input.charAt(i);
880      if (c > 0x7F) {
881        isASCII = false;
882        break;
883      }
884    }
885    return isASCII;
886  }
887
888  static {
889    for (int i = 0; i < SI_UNITS.length; i++) {
890      Unit<?> si = SI_UNITS[i];
891      String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si).getSymbol() : ((AlternateUnit<?>) si).getSymbol();
892      DEFAULT.label(si, symbol);
893      if (isAllASCII(symbol))
894        ASCII.label(si, symbol);
895      for (int j = 0; j < PREFIXES.length; j++) {
896        Unit<?> u = si.transform(CONVERTERS[j]);
897        DEFAULT.label(u, PREFIXES[j] + symbol);
898        if (PREFIXES[j] == "µ") {
899          ASCII.label(u, "micro"); // + symbol);
900        }
901      }
902    }
903    // Special case for KILOGRAM.
904    DEFAULT.label(Units.GRAM, "g");
905    for (int i = 0; i < PREFIXES.length; i++) {
906      if (CONVERTERS[i] == KILO.getConverter()) // TODO should it better
907        // be equals()?
908        continue; // kg is already defined.
909      DEFAULT.label(Units.KILOGRAM.transform(CONVERTERS[i].concatenate(MILLI.getConverter())), PREFIXES[i] + "g");
910      if (PREFIXES[i] == "µ") {
911        ASCII.label(Units.KILOGRAM.transform(CONVERTERS[i].concatenate(MILLI.getConverter())), "microg");
912      }
913    }
914
915    // Alias and ASCIIFormat for Ohm
916    DEFAULT.alias(Units.OHM, "Ohm");
917    ASCII.label(Units.OHM, "Ohm");
918    for (int i = 0; i < PREFIXES.length; i++) {
919      DEFAULT.alias(Units.OHM.transform(CONVERTERS[i]), PREFIXES[i] + "Ohm");
920      ASCII.label(Units.OHM.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Ohm");
921    }
922
923    // Special case for DEGREE_CElSIUS.
924    // DEFAULT.label(Units.CELSIUS, "°C");
925    DEFAULT.label(Units.CELSIUS, "\u00b0C");
926    DEFAULT.alias(Units.CELSIUS, "?");
927    // DEFAULT.alias(Units.CELSIUS, "°C");
928    ASCII.label(Units.CELSIUS, "Celsius");
929    for (int i = 0; i < PREFIXES.length; i++) {
930      DEFAULT.label(Units.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "?");
931      DEFAULT.alias(Units.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "°C");
932      ASCII.label(Units.CELSIUS.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Celsius");
933    }
934
935    DEFAULT.label(Units.PERCENT, "%");
936    DEFAULT.label(Units.KILOGRAM, "kg");
937    DEFAULT.label(Units.METRE, "m");
938    DEFAULT.label(Units.SECOND, "s");
939    DEFAULT.label(Units.MINUTE, "min");
940    DEFAULT.label(Units.HOUR, "h");
941    DEFAULT.label(Units.DAY, "day");
942    DEFAULT.label(Units.WEEK, "week");
943    DEFAULT.label(Units.YEAR, "year");
944    DEFAULT.label(Units.KILOMETRE_PER_HOUR, "km/h");
945    DEFAULT.label(Units.CUBIC_METRE, "\u33A5");
946    ASCII.label(Units.CUBIC_METRE, "m3");
947    ASCII.label(LITRE, "l");
948    DEFAULT.label(LITRE, "l");
949    DEFAULT.label(MetricPrefix.NANO(LITRE), "nl");
950    ASCII.label(MetricPrefix.NANO(LITRE), "nl");
951    DEFAULT.label(MetricPrefix.MICRO(LITRE), "µl");
952    ASCII.label(MetricPrefix.MICRO(LITRE), "microL");
953    ASCII.label(MetricPrefix.MILLI(LITRE), "mL");
954    DEFAULT.label(MetricPrefix.MILLI(LITRE), "ml");
955    ASCII.label(MetricPrefix.CENTI(LITRE), "cL");
956    DEFAULT.label(MetricPrefix.CENTI(LITRE), "cl");
957    ASCII.label(MetricPrefix.DECI(LITRE), "dL");
958    DEFAULT.label(MetricPrefix.DECI(LITRE), "dl");
959    DEFAULT.label(Units.NEWTON, "N");
960    ASCII.label(Units.NEWTON, "N");
961    DEFAULT.label(Units.RADIAN, "rad");
962    ASCII.label(Units.RADIAN, "rad");
963  }
964}