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.3, June 7, 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 labeled.
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    protected 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<?> baseUnit = tfmUnit.getParentUnit();
353        UnitConverter cvtr = tfmUnit.getConverter(); // tfmUnit.getSystemConverter();
354        StringBuilder result = new StringBuilder();
355        String baseUnitName = baseUnit.toString();
356        String prefix = prefixFor(cvtr);
357        if ((baseUnitName.indexOf('\u00b7') >= 0) || (baseUnitName.indexOf('*') >= 0) || (baseUnitName.indexOf('/') >= 0)) {
358          // We could use parentheses whenever baseUnits is an
359          // instanceof ProductUnit, but most ProductUnits have
360          // aliases,
361          // so we'd end up with a lot of unnecessary parentheses.
362          result.append('(');
363          result.append(baseUnitName);
364          result.append(')');
365        } else {
366          result.append(baseUnitName);
367        }
368        if (prefix != null) {
369          result.insert(0, prefix);
370        } else {
371          if (cvtr instanceof AddConverter) {
372            result.append('+');
373            result.append(((AddConverter) cvtr).getOffset());
374          } else if (cvtr instanceof RationalConverter) {
375            double dividend = ((RationalConverter) cvtr).getDividend().doubleValue();
376            if (dividend != 1) {
377              result.append('*');
378              result.append(dividend);
379            }
380            double divisor = ((RationalConverter) cvtr).getDivisor().doubleValue();
381            if (divisor != 1) {
382              result.append('/');
383              result.append(divisor);
384            }
385          } else if (cvtr instanceof MultiplyConverter) {
386            result.append('*');
387            result.append(((MultiplyConverter) cvtr).getFactor());
388          } else { // Other converters.
389            return "[" + baseUnit + "?]";
390          }
391        }
392        return result.toString();
393      }
394      // Compound unit.
395      // if (unit instanceof CompoundUnit) {
396      // CompoundUnit<?> cpdUnit = (CompoundUnit<?>) unit;
397      // return nameFor(cpdUnit.getHigher()).toString() + ":"
398      // + nameFor(cpdUnit.getLower());
399      // }
400      return null; // Product unit.
401    }
402
403    // Returns the prefix for the specified unit converter.
404    protected String prefixFor(UnitConverter converter) {
405      for (int i = 0; i < CONVERTERS.length; i++) {
406        if (CONVERTERS[i].equals(converter)) {
407          return PREFIXES[i];
408        }
409      }
410      return null; // TODO or return blank?
411    }
412
413    // Returns the unit for the specified name.
414    protected Unit<?> unitFor(String name) {
415      Unit<?> unit = _nameToUnit.get(name);
416      if (unit != null)
417        return unit;
418      unit = SYMBOL_TO_UNIT.get(name);
419      return unit;
420    }
421
422    // //////////////////////////
423    // Parsing.
424    @SuppressWarnings({ "rawtypes", "unchecked" })
425    public Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) throws ParserException {
426      int startIndex = pos.getIndex();
427      String name = readIdentifier(csq, pos);
428      Unit unit = unitFor(name);
429      check(unit != null, name + " not recognized", csq, startIndex);
430      return unit;
431    }
432
433    @SuppressWarnings({ "rawtypes", "unchecked" })
434    @Override
435    public Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) throws ParserException {
436      Unit result = AbstractUnit.ONE;
437      int token = nextToken(csq, pos);
438      switch (token) {
439        case IDENTIFIER:
440          result = parseSingleUnit(csq, pos);
441          break;
442        case OPEN_PAREN:
443          pos.setIndex(pos.getIndex() + 1);
444          result = parseProductUnit(csq, pos);
445          token = nextToken(csq, pos);
446          check(token == CLOSE_PAREN, "')' expected", csq, pos.getIndex());
447          pos.setIndex(pos.getIndex() + 1);
448          break;
449      }
450      token = nextToken(csq, pos);
451      while (true) {
452        switch (token) {
453          case EXPONENT:
454            Exponent e = readExponent(csq, pos);
455            if (e.pow != 1) {
456              result = result.pow(e.pow);
457            }
458            if (e.root != 1) {
459              result = result.root(e.root);
460            }
461            break;
462          case MULTIPLY:
463            pos.setIndex(pos.getIndex() + 1);
464            token = nextToken(csq, pos);
465            if (token == INTEGER) {
466              long n = readLong(csq, pos);
467              if (n != 1) {
468                result = result.multiply(n);
469              }
470            } else if (token == FLOAT) {
471              double d = readDouble(csq, pos);
472              if (d != 1.0) {
473                result = result.multiply(d);
474              }
475            } else {
476              result = result.multiply(parseProductUnit(csq, pos));
477            }
478            break;
479          case DIVIDE:
480            pos.setIndex(pos.getIndex() + 1);
481            token = nextToken(csq, pos);
482            if (token == INTEGER) {
483              long n = readLong(csq, pos);
484              if (n != 1) {
485                result = result.divide(n);
486              }
487            } else if (token == FLOAT) {
488              double d = readDouble(csq, pos);
489              if (d != 1.0) {
490                result = result.divide(d);
491              }
492            } else {
493              result = result.divide(parseProductUnit(csq, pos));
494            }
495            break;
496          case PLUS:
497            pos.setIndex(pos.getIndex() + 1);
498            token = nextToken(csq, pos);
499            if (token == INTEGER) {
500              long n = readLong(csq, pos);
501              if (n != 1) {
502                result = result.shift(n);
503              }
504            } else if (token == FLOAT) {
505              double d = readDouble(csq, pos);
506              if (d != 1.0) {
507                result = result.shift(d);
508              }
509            } else {
510              throw new ParserException("not a number", pos.getIndex());
511            }
512            break;
513          case EOF:
514          case CLOSE_PAREN:
515            return result;
516          default:
517            throw new ParserException("unexpected token " + token, pos.getIndex());
518        }
519        token = nextToken(csq, pos);
520      }
521    }
522
523    private static final int EOF = 0;
524    private static final int IDENTIFIER = 1;
525    private static final int OPEN_PAREN = 2;
526    private static final int CLOSE_PAREN = 3;
527    private static final int EXPONENT = 4;
528    private static final int MULTIPLY = 5;
529    private static final int DIVIDE = 6;
530    private static final int PLUS = 7;
531    private static final int INTEGER = 8;
532    private static final int FLOAT = 9;
533
534    private int nextToken(CharSequence csq, ParsePosition pos) {
535      final int length = csq.length();
536      while (pos.getIndex() < length) {
537        char c = csq.charAt(pos.getIndex());
538        if (isUnitIdentifierPart(c)) {
539          return IDENTIFIER;
540        } else if (c == '(') {
541          return OPEN_PAREN;
542        } else if (c == ')') {
543          return CLOSE_PAREN;
544        } else if ((c == '^') || (c == '\u00b9') || (c == '\u00b2') || (c == '\u00b3')) {
545          return EXPONENT;
546        } else if (c == '*') {
547          char c2 = csq.charAt(pos.getIndex() + 1);
548          if (c2 == '*') {
549            return EXPONENT;
550          } else {
551            return MULTIPLY;
552          }
553        } else if (c == '\u00b7') {
554          return MULTIPLY;
555        } else if (c == '/') {
556          return DIVIDE;
557        } else if (c == '+') {
558          return PLUS;
559        } else if ((c == '-') || Character.isDigit(c)) {
560          int index = pos.getIndex() + 1;
561          while ((index < length) && (Character.isDigit(c) || (c == '-') || (c == '.') || (c == 'E'))) {
562            c = csq.charAt(index++);
563            if (c == '.') {
564              return FLOAT;
565            }
566          }
567          return INTEGER;
568        }
569        pos.setIndex(pos.getIndex() + 1);
570      }
571      return EOF;
572    }
573
574    private void check(boolean expr, String message, CharSequence csq, int index) throws ParserException {
575      if (!expr) {
576        throw new ParserException(message + " (in " + csq + " at index " + index + ")", index);
577      }
578    }
579
580    private Exponent readExponent(CharSequence csq, ParsePosition pos) {
581      char c = csq.charAt(pos.getIndex());
582      if (c == '^') {
583        pos.setIndex(pos.getIndex() + 1);
584      } else if (c == '*') {
585        pos.setIndex(pos.getIndex() + 2);
586      }
587      final int length = csq.length();
588      int pow = 0;
589      boolean isPowNegative = false;
590      int root = 0;
591      boolean isRootNegative = false;
592      boolean isRoot = false;
593      while (pos.getIndex() < length) {
594        c = csq.charAt(pos.getIndex());
595        if (c == '\u00b9') {
596          if (isRoot) {
597            root = root * 10 + 1;
598          } else {
599            pow = pow * 10 + 1;
600          }
601        } else if (c == '\u00b2') {
602          if (isRoot) {
603            root = root * 10 + 2;
604          } else {
605            pow = pow * 10 + 2;
606          }
607        } else if (c == '\u00b3') {
608          if (isRoot) {
609            root = root * 10 + 3;
610          } else {
611            pow = pow * 10 + 3;
612          }
613        } else if (c == '-') {
614          if (isRoot) {
615            isRootNegative = true;
616          } else {
617            isPowNegative = true;
618          }
619        } else if ((c >= '0') && (c <= '9')) {
620          if (isRoot) {
621            root = root * 10 + (c - '0');
622          } else {
623            pow = pow * 10 + (c - '0');
624          }
625        } else if (c == ':') {
626          isRoot = true;
627        } else {
628          break;
629        }
630        pos.setIndex(pos.getIndex() + 1);
631      }
632      if (pow == 0)
633        pow = 1;
634      if (root == 0)
635        root = 1;
636      return new Exponent(isPowNegative ? -pow : pow, isRootNegative ? -root : root);
637    }
638
639    private long readLong(CharSequence csq, ParsePosition pos) {
640      final int length = csq.length();
641      int result = 0;
642      boolean isNegative = false;
643      while (pos.getIndex() < length) {
644        char c = csq.charAt(pos.getIndex());
645        if (c == '-') {
646          isNegative = true;
647        } else if ((c >= '0') && (c <= '9')) {
648          result = result * 10 + (c - '0');
649        } else {
650          break;
651        }
652        pos.setIndex(pos.getIndex() + 1);
653      }
654      return isNegative ? -result : result;
655    }
656
657    private double readDouble(CharSequence csq, ParsePosition pos) {
658      final int length = csq.length();
659      int start = pos.getIndex();
660      int end = start + 1;
661      while (end < length) {
662        if ("0123456789+-.E".indexOf(csq.charAt(end)) < 0) {
663          break;
664        }
665        end += 1;
666      }
667      pos.setIndex(end + 1);
668      return Double.parseDouble(csq.subSequence(start, end).toString());
669    }
670
671    private String readIdentifier(CharSequence csq, ParsePosition pos) {
672      final int length = csq.length();
673      int start = pos.getIndex();
674      int i = start;
675      while ((++i < length) && isUnitIdentifierPart(csq.charAt(i))) {
676      }
677      pos.setIndex(i);
678      return csq.subSequence(start, i).toString();
679    }
680
681    // //////////////////////////
682    // Formatting.
683
684    @Override
685    public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
686      String name = nameFor(unit);
687      if (name != null) {
688        return appendable.append(name);
689      }
690      if (!(unit instanceof ProductUnit)) {
691        throw new IllegalArgumentException("Cannot format given Object as a Unit");
692      }
693
694      // Product unit.
695      ProductUnit<?> productUnit = (ProductUnit<?>) unit;
696      int invNbr = 0;
697
698      // Write positive exponents first.
699      boolean start = true;
700      for (int i = 0; i < productUnit.getUnitCount(); i++) {
701        int pow = productUnit.getUnitPow(i);
702        if (pow >= 0) {
703          if (!start) {
704            appendable.append('\u00b7'); // Separator.
705          }
706          name = nameFor(productUnit.getUnit(i));
707          int root = productUnit.getUnitRoot(i);
708          append(appendable, name, pow, root);
709          start = false;
710        } else {
711          invNbr++;
712        }
713      }
714
715      // Write negative exponents.
716      if (invNbr != 0) {
717        if (start) {
718          appendable.append('1'); // e.g. 1/s
719        }
720        appendable.append('/');
721        if (invNbr > 1) {
722          appendable.append('(');
723        }
724        start = true;
725        for (int i = 0; i < productUnit.getUnitCount(); i++) {
726          int pow = productUnit.getUnitPow(i);
727          if (pow < 0) {
728            name = nameFor(productUnit.getUnit(i));
729            int root = productUnit.getUnitRoot(i);
730            if (!start) {
731              appendable.append('\u00b7'); // Separator.
732            }
733            append(appendable, name, -pow, root);
734            start = false;
735          }
736        }
737        if (invNbr > 1) {
738          appendable.append(')');
739        }
740      }
741      return appendable;
742    }
743
744    private void append(Appendable appendable, CharSequence symbol, int pow, int root) throws IOException {
745      appendable.append(symbol);
746      if ((pow != 1) || (root != 1)) {
747        // Write exponent.
748        if ((pow == 2) && (root == 1)) {
749          appendable.append('\u00b2'); // Square
750        } else if ((pow == 3) && (root == 1)) {
751          appendable.append('\u00b3'); // Cubic
752        } else {
753          // Use general exponent form.
754          appendable.append('^');
755          appendable.append(String.valueOf(pow));
756          if (root != 1) {
757            appendable.append(':');
758            appendable.append(String.valueOf(root));
759          }
760        }
761      }
762    }
763
764    // private static final long serialVersionUID = 1L;
765
766    @Override
767    public Unit<?> parse(CharSequence csq) throws ParserException {
768      return parse(csq, 0);
769    }
770
771    @Override
772    protected SymbolMap getSymbols() {
773      return null;
774    }
775
776    protected Unit<?> parse(CharSequence csq, int index) throws IllegalArgumentException {
777      return parse(csq, new ParsePosition(index));
778    }
779
780    @Override
781    protected Unit<?> parse(CharSequence csq, ParsePosition cursor) throws IllegalArgumentException {
782      return parseObject(csq.toString(), cursor);
783    }
784  }
785
786  /**
787   * This class represents the ASCII format.
788   */
789  protected final static class ASCIIFormat extends DefaultFormat {
790
791    @Override
792    protected String nameFor(Unit<?> unit) {
793      // First search if specific ASCII name should be used.
794      String name = _unitToName.get(unit);
795      if (name != null)
796        return name;
797      // Else returns default name.
798      return DEFAULT.nameFor(unit);
799    }
800
801    @Override
802    protected Unit<?> unitFor(String name) {
803      // First search if specific ASCII name.
804      Unit<?> unit = _nameToUnit.get(name);
805      if (unit != null)
806        return unit;
807      // Else returns default mapping.
808      return DEFAULT.unitFor(name);
809    }
810
811    @Override
812    public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
813      String name = nameFor(unit);
814      if (name != null)
815        return appendable.append(name);
816      if (!(unit instanceof ProductUnit))
817        throw new IllegalArgumentException("Cannot format given Object as a Unit");
818
819      ProductUnit<?> productUnit = (ProductUnit<?>) unit;
820      for (int i = 0; i < productUnit.getUnitCount(); i++) {
821        if (i != 0) {
822          appendable.append('*'); // Separator.
823        }
824        name = nameFor(productUnit.getUnit(i));
825        int pow = productUnit.getUnitPow(i);
826        int root = productUnit.getUnitRoot(i);
827        appendable.append(name);
828        if ((pow != 1) || (root != 1)) {
829          // Use general exponent form.
830          appendable.append('^');
831          appendable.append(String.valueOf(pow));
832          if (root != 1) {
833            appendable.append(':');
834            appendable.append(String.valueOf(root));
835          }
836        }
837      }
838      return appendable;
839    }
840
841    @Override
842    public boolean isValidIdentifier(String name) {
843      if ((name == null) || (name.length() == 0))
844        return false;
845      // label must not begin with a digit or mathematical operator
846      return isUnitIdentifierPart(name.charAt(0)) && isAllASCII(name);
847      /*
848       * for (int i = 0; i < name.length(); i++) { if
849       * (!isAsciiCharacter(name.charAt(i))) return false; } return true;
850       */
851    }
852  }
853
854  /**
855   * Holds the unique symbols collection (base units or alternate units).
856   */
857  private static final Map<String, Unit<?>> SYMBOL_TO_UNIT = new HashMap<>();
858
859  // //////////////////////////////////////////////////////////////////////////
860  // Initializes the standard unit database for SI units.
861
862  private static final Unit<?>[] SI_UNITS = { Units.AMPERE, Units.BECQUEREL, Units.CANDELA, Units.COULOMB, Units.FARAD, Units.GRAY, Units.HENRY,
863      Units.HERTZ, Units.JOULE, Units.KATAL, Units.KELVIN, Units.LUMEN, Units.LUX, Units.METRE, Units.MOLE, Units.NEWTON, Units.OHM, Units.PASCAL,
864      Units.RADIAN, Units.SECOND, Units.SIEMENS, Units.SIEVERT, Units.STERADIAN, Units.TESLA, Units.VOLT, Units.WATT, Units.WEBER };
865
866  private static final String[] PREFIXES = { YOTTA.getSymbol(), ZETTA.getSymbol(), EXA.getSymbol(), PETA.getSymbol(), TERA.getSymbol(),
867      GIGA.getSymbol(), MEGA.getSymbol(), KILO.getSymbol(), HECTO.getSymbol(), DEKA.getSymbol(), DECI.getSymbol(), CENTI.getSymbol(),
868      MILLI.getSymbol(), MICRO.getSymbol(), NANO.getSymbol(), PICO.getSymbol(), FEMTO.getSymbol(), ATTO.getSymbol(), ZEPTO.getSymbol(),
869      YOCTO.getSymbol() };
870
871  // TODO we could try retrieving this dynamically in a static {} method from
872  // MetricPrefix if symbols above are also aligned
873  private static final UnitConverter[] CONVERTERS = { YOTTA.getConverter(), ZETTA.getConverter(), EXA.getConverter(), PETA.getConverter(),
874      TERA.getConverter(), GIGA.getConverter(), MEGA.getConverter(), KILO.getConverter(), HECTO.getConverter(), DEKA.getConverter(),
875      DECI.getConverter(), CENTI.getConverter(), MILLI.getConverter(), MICRO.getConverter(), NANO.getConverter(), PICO.getConverter(),
876      FEMTO.getConverter(), ATTO.getConverter(), ZEPTO.getConverter(), YOCTO.getConverter() };
877
878  private static String asciiPrefix(String prefix) {
879    return prefix == "µ" ? "micro" : prefix;
880  }
881
882  // to check if a string only contains US-ASCII characters
883  //
884  protected static boolean isAllASCII(String input) {
885    boolean isASCII = true;
886    for (int i = 0; i < input.length(); i++) {
887      int c = input.charAt(i);
888      if (c > 0x7F) {
889        isASCII = false;
890        break;
891      }
892    }
893    return isASCII;
894  }
895
896  // Initializations
897  static {
898    for (int i = 0; i < SI_UNITS.length; i++) {
899      Unit<?> si = SI_UNITS[i];
900      String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si).getSymbol() : ((AlternateUnit<?>) si).getSymbol();
901      DEFAULT.label(si, symbol);
902      if (isAllASCII(symbol))
903        ASCII.label(si, symbol);
904      for (int j = 0; j < PREFIXES.length; j++) {
905        Unit<?> u = si.transform(CONVERTERS[j]);
906        DEFAULT.label(u, PREFIXES[j] + symbol);
907        if (PREFIXES[j] == "µ") {
908          ASCII.label(u, "micro"); // + symbol);
909        }
910      }
911    }
912    // Special case for KILOGRAM.
913    DEFAULT.label(Units.GRAM, "g");
914    for (int i = 0; i < PREFIXES.length; i++) {
915      if (CONVERTERS[i] == KILO.getConverter()) // TODO should it better
916        // be equals()?
917        continue; // kg is already defined.
918      DEFAULT.label(Units.KILOGRAM.transform(CONVERTERS[i].concatenate(MILLI.getConverter())), PREFIXES[i] + "g");
919      if (PREFIXES[i] == "µ") {
920        ASCII.label(Units.KILOGRAM.transform(CONVERTERS[i].concatenate(MILLI.getConverter())), "microg");
921      }
922    }
923
924    // Alias and ASCIIFormat for Ohm
925    DEFAULT.alias(Units.OHM, "Ohm");
926    ASCII.label(Units.OHM, "Ohm");
927    for (int i = 0; i < PREFIXES.length; i++) {
928      DEFAULT.alias(Units.OHM.transform(CONVERTERS[i]), PREFIXES[i] + "Ohm");
929      ASCII.label(Units.OHM.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Ohm");
930    }
931
932    // Special case for DEGREE_CELSIUS.
933    DEFAULT.label(Units.CELSIUS, "℃");
934    DEFAULT.alias(Units.CELSIUS, "°C");
935    ASCII.label(Units.CELSIUS, "Celsius");
936    for (int i = 0; i < PREFIXES.length; i++) {
937      DEFAULT.label(Units.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "℃");
938      DEFAULT.alias(Units.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "°C");
939      ASCII.label(Units.CELSIUS.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Celsius");
940    }
941
942    DEFAULT.label(Units.PERCENT, "%");
943    DEFAULT.label(Units.KILOGRAM, "kg");
944    ASCII.label(Units.KILOGRAM, "kg");
945    DEFAULT.label(Units.METRE, "m");
946    ASCII.label(Units.METRE, "m");
947    DEFAULT.label(Units.SECOND, "s");
948    ASCII.label(Units.SECOND, "s");
949    DEFAULT.label(Units.MINUTE, "min");
950    DEFAULT.label(Units.HOUR, "h");
951    DEFAULT.label(Units.DAY, "day");
952    DEFAULT.alias(Units.DAY, "d");
953    DEFAULT.label(Units.WEEK, "week");
954    DEFAULT.label(Units.YEAR, "year");
955    DEFAULT.alias(Units.YEAR, "days365");
956    ASCII.label(Units.KILOMETRE_PER_HOUR, "km/h");
957    DEFAULT.label(Units.KILOMETRE_PER_HOUR, "km/h");
958    DEFAULT.label(Units.CUBIC_METRE, "\u33A5");
959    ASCII.label(Units.CUBIC_METRE, "m3");
960    ASCII.label(Units.LITRE, "l");
961    DEFAULT.label(Units.LITRE, "l");
962    DEFAULT.label(MetricPrefix.MICRO(Units.LITRE), "µl");
963    ASCII.label(MetricPrefix.MICRO(Units.LITRE), "microL");
964    ASCII.label(MetricPrefix.MILLI(Units.LITRE), "mL");
965    DEFAULT.label(MetricPrefix.MILLI(Units.LITRE), "ml");
966    ASCII.label(MetricPrefix.CENTI(Units.LITRE), "cL");
967    DEFAULT.label(MetricPrefix.CENTI(Units.LITRE), "cl");
968    ASCII.label(MetricPrefix.DECI(Units.LITRE), "dL");
969    DEFAULT.label(MetricPrefix.DECI(Units.LITRE), "dl");
970    DEFAULT.label(Units.NEWTON, "N");
971    ASCII.label(Units.NEWTON, "N");
972    DEFAULT.label(Units.RADIAN, "rad");
973    ASCII.label(Units.RADIAN, "rad");
974
975    DEFAULT.label(AbstractUnit.ONE, "one");
976    ASCII.label(AbstractUnit.ONE, "one");
977  }
978}