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