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