001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2021, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tech.units.indriya.format;
031
032import java.io.IOException;
033import java.text.FieldPosition;
034import java.text.ParsePosition;
035import java.util.HashMap;
036import java.util.Map;
037import java.util.stream.Collectors;
038import java.util.stream.Stream;
039
040import javax.measure.BinaryPrefix;
041import javax.measure.MeasurementError;
042import javax.measure.MetricPrefix;
043import javax.measure.Prefix;
044import javax.measure.Quantity;
045import javax.measure.Unit;
046import javax.measure.UnitConverter;
047import javax.measure.format.MeasurementParseException;
048import javax.measure.format.UnitFormat;
049
050import static javax.measure.MetricPrefix.MICRO;
051
052import tech.units.indriya.AbstractUnit;
053import tech.units.indriya.function.AddConverter;
054import tech.units.indriya.function.MultiplyConverter;
055import tech.units.indriya.function.RationalNumber;
056import tech.units.indriya.unit.AlternateUnit;
057import tech.units.indriya.unit.AnnotatedUnit;
058import tech.units.indriya.unit.BaseUnit;
059import tech.units.indriya.unit.ProductUnit;
060import tech.units.indriya.unit.TransformedUnit;
061import tech.units.indriya.unit.Units;
062
063import static tech.units.indriya.format.FormatConstants.MIDDLE_DOT;
064
065/**
066 * <p>
067 * This class implements the {@link UnitFormat} interface for formatting and parsing {@link Unit units}.
068 * </p>
069 * 
070 * <p>
071 * For all SI units, the <b>20 SI prefixes</b> used to form decimal multiples and sub-multiples are recognized. As well as the <b>8 binary prefixes</b>.<br>
072 * {@link Units} are directly recognized. For example:<br>
073 * <code>
074 *        UnitFormat format = SimpleUnitFormat.getInstance();<br>
075 *        format.parse("m°C").equals(MetricPrefix.MILLI(Units.CELSIUS));<br>
076 *        format.parse("kW").equals(MetricPrefix.KILO(Units.WATT));<br>
077 *        format.parse("ft").equals(Units.METRE.multiply(0.3048))</code>
078 * </p>
079 * 
080 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
081 * @author <a href="mailto:werner@units.tech">Werner Keil</a>
082 * @author Eric Russell
083 * @author Andi Huber
084 * @version 2.3, Nov. 27, 2020
085 * @since 1.0
086 */
087public abstract class SimpleUnitFormat extends AbstractUnitFormat {
088    /**
089     * 
090     */
091    // private static final long serialVersionUID = 4149424034841739785L;#
092
093    /**
094     * Flavor of this format
095     *
096     * @author Werner
097     *
098     */
099    public static enum Flavor {
100        Default, ASCII
101    }
102
103    // Initializes the standard unit database for SI units.
104
105    private static final Unit<?>[] METRIC_UNITS = { Units.AMPERE, Units.BECQUEREL, Units.CANDELA, Units.COULOMB, Units.FARAD, Units.GRAY, Units.HENRY,
106            Units.HERTZ, Units.JOULE, Units.KATAL, Units.KELVIN, Units.LUMEN, Units.LUX, Units.METRE, Units.MOLE, Units.NEWTON, Units.OHM, Units.PASCAL,
107            Units.RADIAN, Units.SECOND, Units.SIEMENS, Units.SIEVERT, Units.STERADIAN, Units.TESLA, Units.VOLT, Units.WATT, Units.WEBER };
108
109    private static final String[] METRIC_PREFIX_SYMBOLS =  
110            Stream.of(MetricPrefix.values())
111            .map(Prefix::getSymbol)
112            .collect(Collectors.toList())
113            .toArray(new String[] {});
114
115    // TODO try to consolidate those
116    private static final UnitConverter[] METRIC_PREFIX_CONVERTERS =  
117            Stream.of(MetricPrefix.values())
118            .map(MultiplyConverter::ofPrefix)
119            .collect(Collectors.toList())
120            .toArray(new UnitConverter[] {});
121
122    private static final String[] BINARY_PREFIX_SYMBOLS =  
123            Stream.of(BinaryPrefix.values())
124            .map(Prefix::getSymbol)
125            .collect(Collectors.toList())
126            .toArray(new String[] {});
127
128    private static final UnitConverter[] BINARY_PREFIX_CONVERTERS =  
129            Stream.of(BinaryPrefix.values())
130            .map(MultiplyConverter::ofPrefix)
131            .collect(Collectors.toList())
132            .toArray(new UnitConverter[] {});
133
134    private static final String MU = "\u03bc";
135
136    /**
137     * Returns the unit format for the default locale (format used by {@link AbstractUnit#parse(CharSequence) AbstractUnit.parse(CharSequence)} and
138     * {@link Unit#toString() Unit.toString()}).
139     *
140     * @return the default unit format (locale sensitive).
141     */
142    public static SimpleUnitFormat getInstance() {
143        return getInstance(Flavor.Default);
144    }
145
146    /**
147     * Returns the {@link SimpleUnitFormat} in the desired {@link Flavor}
148     *
149     * @return the instance for the given {@link Flavor}.
150     */
151    public static SimpleUnitFormat getInstance(Flavor flavor) {
152        switch (flavor) {
153        case ASCII:
154            return SimpleUnitFormat.ASCII;
155        default:
156            return DEFAULT;
157        }
158    }
159
160    /**
161     * Base constructor.
162     */
163    protected SimpleUnitFormat() {
164    }
165
166    /**
167     * Formats the specified unit.
168     *
169     * @param unit
170     *          the unit to format.
171     * @param appendable
172     *          the appendable destination.
173     * @throws IOException
174     *           if an error occurs.
175     */
176    public abstract Appendable format(Unit<?> unit, Appendable appendable) throws IOException;
177
178    /**
179     * Parses a sequence of character to produce a unit or a rational product of unit.
180     *
181     * @param csq
182     *          the <code>CharSequence</code> to parse.
183     * @param pos
184     *          an object holding the parsing index and error position.
185     * @return an {@link Unit} parsed from the character sequence.
186     * @throws IllegalArgumentException
187     *           if the character sequence contains an illegal syntax.
188     */
189    @SuppressWarnings("rawtypes")
190    public abstract Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) throws MeasurementParseException;
191
192    /**
193     * Parses a sequence of character to produce a single unit.
194     *
195     * @param csq
196     *          the <code>CharSequence</code> to parse.
197     * @param pos
198     *          an object holding the parsing index and error position.
199     * @return an {@link Unit} parsed from the character sequence.
200     * @throws IllegalArgumentException
201     *           if the character sequence does not contain a valid unit identifier.
202     */
203    @SuppressWarnings("rawtypes")
204    public abstract Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) throws MeasurementParseException;
205
206    /**
207     * Attaches a system-wide label to the specified unit. For example: <code> SimpleUnitFormat.getInstance().label(DAY.multiply(365), "year");
208     * SimpleUnitFormat.getInstance().label(METER.multiply(0.3048), "ft"); </code> If the specified label is already associated to an unit the previous
209     * association is discarded or ignored.
210     *
211     * @param unit
212     *          the unit being labeled.
213     * @param label
214     *          the new label for this unit.
215     * @throws IllegalArgumentException
216     *           if the label is not a {@link SimpleUnitFormat#isValidIdentifier(String)} valid identifier.
217     */
218    public abstract void label(Unit<?> unit, String label);
219
220    /**
221     * Attaches a system-wide alias to this unit. Multiple aliases may be attached to the same unit. Aliases are used during parsing to recognize
222     * different variants of the same unit. For example: <code> SimpleUnitFormat.getInstance().alias(METER.multiply(0.3048), "foot");
223     * SimpleUnitFormat.getInstance().alias(METER.multiply(0.3048), "feet"); SimpleUnitFormat.getInstance().alias(METER, "meter");
224     * SimpleUnitFormat.getInstance().alias(METER, "metre"); </code> If the specified label is already associated to an unit the previous association is
225     * discarded or ignored.
226     *
227     * @param unit
228     *          the unit being aliased.
229     * @param alias
230     *          the alias attached to this unit.
231     * @throws IllegalArgumentException
232     *           if the label is not a {@link SimpleUnitFormat#isValidIdentifier(String)} valid identifier.
233     */
234    public abstract void alias(Unit<?> unit, String alias);
235
236    /**
237     * Indicates if the specified name can be used as unit identifier.
238     *
239     * @param name
240     *          the identifier to be tested.
241     * @return <code>true</code> if the name specified can be used as label or alias for this format;<code>false</code> otherwise.
242     */
243    protected abstract boolean isValidIdentifier(String name);
244
245    /**
246     * Formats an unit and appends the resulting text to a given string buffer (implements <code>java.text.Format</code>).
247     *
248     * @param unit
249     *          the unit to format.
250     * @param toAppendTo
251     *          where the text is to be appended
252     * @param pos
253     *          the field position (not used).
254     * @return <code>toAppendTo</code>
255     */
256    public final StringBuffer format(Object unit, final StringBuffer toAppendTo, FieldPosition pos) {
257        try {
258            final Object dest = toAppendTo;
259            if (dest instanceof Appendable) {
260                format((Unit<?>) unit, (Appendable) dest);
261            } else { // When retroweaver is used to produce 1.4 binaries. TODO is this still relevant?
262                format((Unit<?>) unit, new Appendable() {
263                    public Appendable append(char arg0) throws IOException {
264                        toAppendTo.append(arg0);
265                        return null;
266                    }
267                    public Appendable append(CharSequence arg0) throws IOException {
268                        toAppendTo.append(arg0);
269                        return null;
270                    }
271                    public Appendable append(CharSequence arg0, int arg1, int arg2) throws IOException {
272                        toAppendTo.append(arg0.subSequence(arg1, arg2));
273                        return null;
274                    }
275                });
276            }
277            return toAppendTo;
278        } catch (IOException e) {
279            throw new MeasurementError(e); // Should never happen.
280        }
281    }
282
283    /**
284     * Parses the text from a string to produce an object (implements <code>java.text.Format</code>).
285     *
286     * @param source
287     *          the string source, part of which should be parsed.
288     * @param pos
289     *          the cursor position.
290     * @return the corresponding unit or <code>null</code> if the string cannot be parsed.
291     */
292    public final Unit<?> parseObject(String source, ParsePosition pos) throws MeasurementParseException {
293        return parseProductUnit(source, pos);
294    }
295
296    /**
297     * This class represents an exponent with both a power (numerator) and a root (denominator).
298     */
299    private static class Exponent {
300        public final int pow;
301        public final int root;
302
303        public Exponent(int pow, int root) {
304            this.pow = pow;
305            this.root = root;
306        }
307    }
308
309    /**
310     * This class represents the standard format.
311     */
312    protected static class DefaultFormat extends SimpleUnitFormat {
313        private static enum Token { EOF, IDENTIFIER, OPEN_PAREN, CLOSE_PAREN, EXPONENT, MULTIPLY, DIVIDE, 
314            PLUS, INTEGER, FLOAT };
315
316            /**
317             * Holds the name to unit mapping.
318             */
319            protected final Map<String, Unit<?>> nameToUnit = new HashMap<>();
320
321            /**
322             * Holds the unit to name mapping.
323             */
324            protected final Map<Unit<?>, String> unitToName = new HashMap<>();
325
326            @Override
327            public String toString() {
328                return "SimpleUnitFormat";
329            }
330
331            @Override
332            public void label(Unit<?> unit, String label) {
333                if (!isValidIdentifier(label))
334                    throw new IllegalArgumentException("Label: " + label + " is not a valid identifier.");
335                synchronized (this) {
336                    nameToUnit.put(label, unit);
337                    unitToName.put(unit, label);
338                }
339            }
340
341            @Override
342            public void alias(Unit<?> unit, String alias) {
343                if (!isValidIdentifier(alias))
344                    throw new IllegalArgumentException("Alias: " + alias + " is not a valid identifier.");
345                synchronized (this) {
346                    nameToUnit.put(alias, unit);
347                }
348            }
349
350            @Override
351            protected boolean isValidIdentifier(String name) {
352                if ((name == null) || (name.length() == 0))
353                    return false;
354                return isUnitIdentifierPart(name.charAt(0));
355            }
356
357            protected static boolean isUnitIdentifierPart(char ch) {
358                return Character.isLetter(ch)
359                        || (!Character.isWhitespace(ch) && !Character.isDigit(ch) && (ch != MIDDLE_DOT) && (ch != '*') && (ch != '/') && (ch != '(') && (ch != ')')
360                                && (ch != '[') && (ch != ']') && (ch != '\u00b9') && (ch != '\u00b2') && (ch != '\u00b3') && (ch != '^') && (ch != '+') && (ch != '-'));
361            }
362
363            // Returns the name for the specified unit or null if product unit.
364            protected String nameFor(Unit<?> unit) {
365                // Searches label database.
366                String label = unitToName.get(unit);
367                if (label != null)
368                    return label;
369                if (unit instanceof BaseUnit)
370                    return ((BaseUnit<?>) unit).getSymbol();
371                if (unit instanceof AlternateUnit)
372                    return ((AlternateUnit<?>) unit).getSymbol();
373                if (unit instanceof TransformedUnit) {
374                    TransformedUnit<?> tfmUnit = (TransformedUnit<?>) unit;
375                    if (tfmUnit.getSymbol() != null) {
376                        return tfmUnit.getSymbol();
377                    }
378                    Unit<?> baseUnit = tfmUnit.getParentUnit();
379                    UnitConverter cvtr = tfmUnit.getConverter(); // tfmUnit.getSystemConverter();
380                    StringBuilder result = new StringBuilder();
381                    String baseUnitName = baseUnit.toString();
382                    String prefix = prefixFor(cvtr);
383                    if ((baseUnitName.indexOf(MIDDLE_DOT) >= 0) || (baseUnitName.indexOf('*') >= 0) || (baseUnitName.indexOf('/') >= 0)) {
384                        // We could use parentheses whenever baseUnits is an
385                        // instanceof ProductUnit, but most ProductUnits have
386                        // aliases,
387                        // so we'd end up with a lot of unnecessary parentheses.
388                        result.append('(');
389                        result.append(baseUnitName);
390                        result.append(')');
391                    } else {
392                        result.append(baseUnitName);
393                    }
394                    if (prefix != null) {
395                        result.insert(0, prefix);
396                    } else {
397                        if (cvtr instanceof AddConverter) {
398                            result.append('+');
399                            result.append(((AddConverter) cvtr).getOffset());
400                        } else if (cvtr instanceof MultiplyConverter) {
401                            Number scaleFactor = ((MultiplyConverter) cvtr).getFactor();
402                            if(scaleFactor instanceof RationalNumber) {
403
404                                RationalNumber rational = (RationalNumber)scaleFactor;
405                                RationalNumber reciprocal = rational.reciprocal();
406                                if(reciprocal.isInteger()) {
407                                    result.append('/');
408                                    result.append(reciprocal.toString()); // renders as integer
409                                } else {
410                                    result.append('*');
411                                    result.append(scaleFactor);  
412                                }
413
414                            } else {
415                                result.append('*');
416                                result.append(scaleFactor);
417                            }
418
419                        } else { // Other converters.
420                            return "[" + baseUnit + "?]";
421                        }
422                    }
423                    return result.toString();
424                }
425                if (unit instanceof AnnotatedUnit<?>) {
426                    AnnotatedUnit<?> annotatedUnit = (AnnotatedUnit<?>) unit;
427                    final StringBuilder annotable = new StringBuilder(nameFor(annotatedUnit.getActualUnit()));
428                    if (annotatedUnit.getAnnotation() != null) {
429                        annotable.append('{'); // TODO maybe also configure this one similar to mix delimiter
430                        annotable.append(annotatedUnit.getAnnotation());
431                        annotable.append('}');
432                    }
433                    return annotable.toString();
434                }
435                // mixed unit.
436                /*      if (unit instanceof MixedUnit) {
437        MixedUnit<?> mixUnit = (MixedUnit<?>) unit;
438        final StringBuilder mixer = new StringBuilder();
439        final int partSize = mixUnit.getUnits().size();
440        int pos = 0;
441        for (Unit<?> part : mixUnit.getUnits()) {
442            mixer.append(nameFor(part));
443            pos++;
444            if (mixer.length() > 0 && pos < partSize) 
445                mixer.append(";"); // FIXME we need a more flexible pattern here
446        }
447        return mixer.toString();
448      }
449                 */
450                return null; // Product unit.
451            }
452
453            // Returns the prefix for the specified unit converter.
454            protected String prefixFor(UnitConverter converter) {
455                for (int i = 0; i < METRIC_PREFIX_CONVERTERS.length; i++) {
456                    if (METRIC_PREFIX_CONVERTERS[i].equals(converter)) {
457                        return METRIC_PREFIX_SYMBOLS[i];
458                    }
459                }
460                for (int j = 0; j < BINARY_PREFIX_CONVERTERS.length; j++) {
461                    if (BINARY_PREFIX_CONVERTERS[j].equals(converter)) {
462                        return BINARY_PREFIX_SYMBOLS[j];
463                    }
464                }
465                return null; // TODO or return blank?
466            }
467
468            // Returns the unit for the specified name.
469            protected Unit<?> unitFor(String name) {
470                Unit<?> unit = nameToUnit.get(name);
471                if (unit != null) {
472                    return unit;
473                } else {
474                    unit = SYMBOL_TO_UNIT.get(name);
475                }
476                return unit;
477            }
478
479            // //////////////////////////
480            // Parsing.
481            @SuppressWarnings({ "rawtypes", "unchecked" })
482            public Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) throws MeasurementParseException {
483                int startIndex = pos.getIndex();
484                String name = readIdentifier(csq, pos);
485                Unit unit = unitFor(name);
486                check(unit != null, name + " not recognized", csq, startIndex);
487                return unit;
488            }
489
490            @SuppressWarnings({ "rawtypes", "unchecked" })
491            @Override
492            public Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) throws MeasurementParseException {
493                Unit result = null;
494                if (csq == null) {
495                        throw new MeasurementParseException("Cannot parse null", csq, pos.getIndex());
496                } else {
497                        result = unitFor(csq.toString());
498                        if (result != null)
499                                return result;
500                }
501                result = AbstractUnit.ONE;
502                Token token = nextToken(csq, pos);
503                switch (token) {
504                case IDENTIFIER:
505                    result = parseSingleUnit(csq, pos);
506                    break;
507                case OPEN_PAREN:
508                    pos.setIndex(pos.getIndex() + 1);
509                    result = parseProductUnit(csq, pos);
510                    token = nextToken(csq, pos);
511                    check(token == Token.CLOSE_PAREN, "')' expected", csq, pos.getIndex());
512                    pos.setIndex(pos.getIndex() + 1);
513                    break;
514                default:
515                    break;
516                }
517                token = nextToken(csq, pos);
518                while (true) {
519                    switch (token) {
520                    case EXPONENT:
521                        Exponent e = readExponent(csq, pos);
522                        if (e.pow != 1) {
523                            result = result.pow(e.pow);
524                        }
525                        if (e.root != 1) {
526                            result = result.root(e.root);
527                        }
528                        break;
529                    case MULTIPLY:
530                        pos.setIndex(pos.getIndex() + 1);
531                        token = nextToken(csq, pos);
532                        if (token == Token.INTEGER) {
533                            long n = readLong(csq, pos);
534                            if (n != 1) {
535                                result = result.multiply(n);
536                            }
537                        } else if (token == Token.FLOAT) {
538                            double d = readDouble(csq, pos);
539                            if (d != 1.0) {
540                                result = result.multiply(d);
541                            }
542                        } else {
543                            result = result.multiply(parseProductUnit(csq, pos));
544                        }
545                        break;
546                    case DIVIDE:
547                        pos.setIndex(pos.getIndex() + 1);
548                        token = nextToken(csq, pos);
549                        if (token == Token.INTEGER) {
550                            long n = readLong(csq, pos);
551                            if (n != 1) {
552                                result = result.divide(n);
553                            }
554                        } else if (token == Token.FLOAT) {
555                            double d = readDouble(csq, pos);
556                            if (d != 1.0) {
557                                result = result.divide(d);
558                            }
559                        } else {
560                            result = result.divide(parseProductUnit(csq, pos));
561                        }
562                        break;
563                    case PLUS:
564                        pos.setIndex(pos.getIndex() + 1);
565                        token = nextToken(csq, pos);
566                        if (token == Token.INTEGER) {
567                            long n = readLong(csq, pos);
568                            if (n != 1) {
569                                result = result.shift(n);
570                            }
571                        } else if (token == Token.FLOAT) {
572                            double d = readDouble(csq, pos);
573                            if (d != 1.0) {
574                                result = result.shift(d);
575                            }
576                        } else {
577                            throw new MeasurementParseException("not a number", csq, pos.getIndex());
578                        }
579                        break;
580                    case EOF:
581                    case CLOSE_PAREN:
582                        return result;
583                    default:
584                        throw new MeasurementParseException("unexpected token " + token, csq, pos.getIndex());
585                    }
586                    token = nextToken(csq, pos);
587                }
588            }
589
590            private static Token nextToken(CharSequence csq, ParsePosition pos) {
591                final int length = csq.length();
592                while (pos.getIndex() < length) {
593                    char c = csq.charAt(pos.getIndex());
594                    if (isUnitIdentifierPart(c)) {
595                        return Token.IDENTIFIER;
596                    } else if (c == '(') {
597                        return Token.OPEN_PAREN;
598                    } else if (c == ')') {
599                        return Token.CLOSE_PAREN;
600                    } else if ((c == '^') || (c == '\u00b9') || (c == '\u00b2') || (c == '\u00b3')) {
601                        return Token.EXPONENT;
602                    } else if (c == '*') {
603                        if (csq.length() == pos.getIndex() + 1) {
604                            throw new MeasurementParseException("unexpected token " + Token.EOF, csq, pos.getIndex()); // return ;
605                        }
606                        char c2 = csq.charAt(pos.getIndex() + 1);
607                        return c2 == '*' ? Token.EXPONENT : Token.MULTIPLY;
608                    } else if (c == MIDDLE_DOT) {
609                        return Token.MULTIPLY;
610                    } else if (c == '/') {
611                        return Token.DIVIDE;
612                    } else if (c == '+') {
613                        return Token.PLUS;
614                    } else if ((c == '-') || Character.isDigit(c)) {
615                        int index = pos.getIndex() + 1;
616                        while ((index < length) && (Character.isDigit(c) || (c == '-') || (c == '.') || (c == 'E'))) {
617                            c = csq.charAt(index++);
618                            if (c == '.') {
619                                return Token.FLOAT;
620                            }
621                        }
622                        return Token.INTEGER;
623                    }
624                    pos.setIndex(pos.getIndex() + 1);
625                }
626                return Token.EOF;
627            }
628
629            private static void check(boolean expr, String message, CharSequence csq, int index) throws MeasurementParseException {
630                if (!expr) {
631                    throw new MeasurementParseException(message + " (in " + csq + " at index " + index + ")", index);
632                }
633            }
634
635            private static Exponent readExponent(CharSequence csq, ParsePosition pos) {
636                char c = csq.charAt(pos.getIndex());
637                if (c == '^') {
638                    pos.setIndex(pos.getIndex() + 1);
639                } else if (c == '*') {
640                    pos.setIndex(pos.getIndex() + 2);
641                }
642                final int length = csq.length();
643                int pow = 0;
644                boolean isPowNegative = false;
645                boolean parseRoot = false;
646
647                POWERLOOP: while (pos.getIndex() < length) {
648                    c = csq.charAt(pos.getIndex());
649                    switch(c) {
650                    case '-': isPowNegative = true; break;
651                    case '\u00b9': pow = pow * 10 + 1; break;
652                    case '\u00b2': pow = pow * 10 + 2; break;
653                    case '\u00b3': pow = pow * 10 + 3; break;
654                    case ':': parseRoot = true; break POWERLOOP; 
655                    default: 
656                        if (c >= '0' && c <= '9') pow = pow * 10 + (c - '0');  
657                        else break POWERLOOP; 
658                    }
659                    pos.setIndex(pos.getIndex() + 1);
660                }
661                if (pow == 0) pow = 1;
662
663                int root = 0;
664                boolean isRootNegative = false;
665                if (parseRoot) {
666                    pos.setIndex(pos.getIndex() + 1);
667                    ROOTLOOP: while (pos.getIndex() < length) {
668                        c = csq.charAt(pos.getIndex());
669                        switch(c) {
670                        case '-': isRootNegative = true; break;
671                        case '\u00b9': root = root * 10 + 1; break;
672                        case '\u00b2': root = root * 10 + 2; break;
673                        case '\u00b3': root = root * 10 + 3; break;
674                        default: 
675                            if (c >= '0' && c <= '9') root = root * 10 + (c - '0');  
676                            else break ROOTLOOP; 
677                        }
678                        pos.setIndex(pos.getIndex() + 1);
679                    }
680                }
681                if (root == 0) root = 1;
682
683                return new Exponent(isPowNegative ? -pow : pow, isRootNegative ? -root : root);
684            }
685
686            private static long readLong(CharSequence csq, ParsePosition pos) {
687                final int length = csq.length();
688                int result = 0;
689                boolean isNegative = false;
690                while (pos.getIndex() < length) {
691                    char c = csq.charAt(pos.getIndex());
692                    if (c == '-') {
693                        isNegative = true;
694                    } else if ((c >= '0') && (c <= '9')) {
695                        result = result * 10 + (c - '0');
696                    } else {
697                        break;
698                    }
699                    pos.setIndex(pos.getIndex() + 1);
700                }
701                return isNegative ? -result : result;
702            }
703
704            private static double readDouble(CharSequence csq, ParsePosition pos) {
705                final int length = csq.length();
706                int start = pos.getIndex();
707                int end = start + 1;
708                while (end < length) {
709                    if ("0123456789+-.E".indexOf(csq.charAt(end)) < 0) {
710                        break;
711                    }
712                    end += 1;
713                }
714                pos.setIndex(end + 1);
715                return Double.parseDouble(csq.subSequence(start, end).toString());
716            }
717
718            private static String readIdentifier(CharSequence csq, ParsePosition pos) {
719                final int length = csq.length();
720                int start = pos.getIndex();
721                int i = start;
722                while ((++i < length) && isUnitIdentifierPart(csq.charAt(i))) {
723                }
724                pos.setIndex(i);
725                return csq.subSequence(start, i).toString();
726            }
727
728            // //////////////////////////
729            // Formatting.
730
731            @Override
732            public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
733                String name = nameFor(unit);
734                if (name != null) {
735                    return appendable.append(name);
736                }
737                if (!(unit instanceof ProductUnit)) {
738                    throw new IllegalArgumentException("Cannot format given Object as a Unit");
739                }
740
741                // Product unit.
742                ProductUnit<?> productUnit = (ProductUnit<?>) unit;
743
744                // Special case: self-powered product unit
745                if (productUnit.getUnitCount() == 1 && productUnit.getUnit(0) instanceof ProductUnit) {
746                    final ProductUnit<?> powerUnit = (ProductUnit<?>) productUnit.getUnit(0);
747                    // is the sub-unit known under a given label?
748                    if (nameFor(powerUnit) == null)
749                        // apply the power to the sub-units and format those instead
750                        return format(ProductUnit.ofPow(powerUnit, productUnit.getUnitPow(0)), appendable);
751                }
752
753                int invNbr = 0;
754
755                // Write positive exponents first.
756                boolean start = true;
757                for (int i = 0; i < productUnit.getUnitCount(); i++) {
758                    int pow = productUnit.getUnitPow(i);
759                    if (pow >= 0) {
760                        if (!start) {
761                            appendable.append(MIDDLE_DOT); // Separator.
762                        }
763                        name = nameFor(productUnit.getUnit(i));
764                        int root = productUnit.getUnitRoot(i);
765                        append(appendable, name, pow, root);
766                        start = false;
767                    } else {
768                        invNbr++;
769                    }
770                }
771
772                // Write negative exponents.
773                if (invNbr != 0) {
774                    if (start) {
775                        appendable.append('1'); // e.g. 1/s
776                    }
777                    appendable.append('/');
778                    if (invNbr > 1) {
779                        appendable.append('(');
780                    }
781                    start = true;
782                    for (int i = 0; i < productUnit.getUnitCount(); i++) {
783                        int pow = productUnit.getUnitPow(i);
784                        if (pow < 0) {
785                            name = nameFor(productUnit.getUnit(i));
786                            int root = productUnit.getUnitRoot(i);
787                            if (!start) {
788                                appendable.append(MIDDLE_DOT); // Separator.
789                            }
790                            append(appendable, name, -pow, root);
791                            start = false;
792                        }
793                    }
794                    if (invNbr > 1) {
795                        appendable.append(')');
796                    }
797                }
798                return appendable;
799            }
800
801            private static void append(Appendable appendable, CharSequence symbol, int pow, int root) throws IOException {
802                appendable.append(symbol);
803                if ((pow != 1) || (root != 1)) {
804                    // Write exponent.
805                    if ((pow == 2) && (root == 1)) {
806                        appendable.append('\u00b2'); // Square
807                    } else if ((pow == 3) && (root == 1)) {
808                        appendable.append('\u00b3'); // Cubic
809                    } else {
810                        // Use general exponent form.
811                        appendable.append('^');
812                        appendable.append(String.valueOf(pow));
813                        if (root != 1) {
814                            appendable.append(':');
815                            appendable.append(String.valueOf(root));
816                        }
817                    }
818                }
819            }
820
821            // private static final long serialVersionUID = 1L;
822
823            @Override
824            public Unit<?> parse(CharSequence csq) throws MeasurementParseException {
825                return parse(csq, 0);
826            }
827
828            protected Unit<?> parse(CharSequence csq, int index) throws IllegalArgumentException {
829                return parse(csq, new ParsePosition(index));
830            }
831
832            @Override
833            public Unit<?> parse(CharSequence csq, ParsePosition cursor) throws IllegalArgumentException {
834                return parseObject(csq.toString(), cursor);
835            }
836    }
837
838    /**
839     * This class represents the ASCII format.
840     */
841    protected final static class ASCIIFormat extends DefaultFormat {
842
843        @Override
844        protected String nameFor(Unit<?> unit) {
845            // First search if specific ASCII name should be used.
846            String name = unitToName.get(unit);
847            if (name != null)
848                return name;
849            // Else returns default name.
850            return DEFAULT.nameFor(unit);
851        }
852
853        @Override
854        protected Unit<?> unitFor(String name) {
855            // First search if specific ASCII name.
856            Unit<?> unit = nameToUnit.get(name);
857            if (unit != null)
858                return unit;
859            // Else returns default mapping.
860            return DEFAULT.unitFor(name);
861        }
862
863        @Override
864        public String toString() {
865            return "SimpleUnitFormat - ASCII";
866        }
867
868        @Override
869        public Appendable format(Unit<?> unit, Appendable appendable) throws IOException {
870            String name = nameFor(unit);
871            if (name != null)
872                return appendable.append(name);
873            if (!(unit instanceof ProductUnit))
874                throw new IllegalArgumentException("Cannot format given Object as a Unit");
875
876            ProductUnit<?> productUnit = (ProductUnit<?>) unit;
877            for (int i = 0; i < productUnit.getUnitCount(); i++) {
878                if (i != 0) {
879                    appendable.append('*'); // Separator.
880                }
881                name = nameFor(productUnit.getUnit(i));
882                int pow = productUnit.getUnitPow(i);
883                int root = productUnit.getUnitRoot(i);
884                appendable.append(name);
885                if ((pow != 1) || (root != 1)) {
886                    // Use general exponent form.
887                    appendable.append('^');
888                    appendable.append(String.valueOf(pow));
889                    if (root != 1) {
890                        appendable.append(':');
891                        appendable.append(String.valueOf(root));
892                    }
893                }
894            }
895            return appendable;
896        }
897
898        @Override
899        protected boolean isValidIdentifier(String name) {
900            if ((name == null) || (name.length() == 0))
901                return false;
902            // label must not begin with a digit or mathematical operator
903            return isUnitIdentifierPart(name.charAt(0)) && isAllASCII(name);
904            /*
905             * for (int i = 0; i < name.length(); i++) { if
906             * (!isAsciiCharacter(name.charAt(i))) return false; } return true;
907             */
908        }
909    }
910
911    /**
912     * Holds the unique symbols collection (base units or alternate units).
913     */
914    private static final Map<String, Unit<?>> SYMBOL_TO_UNIT = new HashMap<>();
915
916    private static String asciiPrefix(String prefix) {
917        return "µ".equals(prefix) ? "micro" : prefix;
918    }
919
920    private static String asciiSymbol(String s) {
921        return "Ω".equals(s) ? "Ohm" : s;
922    }
923
924    /** to check if a string only contains US-ASCII characters */
925    protected static boolean isAllASCII(String input) {
926        boolean isASCII = true;
927        for (int i = 0; i < input.length(); i++) {
928            int c = input.charAt(i);
929            if (c > 0x7F) {
930                isASCII = false;
931                break;
932            }
933        }
934        return isASCII;
935    }
936    
937    // -- INIT
938    
939    static {
940        // Hack, somehow µg is not found.
941        SYMBOL_TO_UNIT.put(MetricPrefix.MICRO.getSymbol() + "g", MICRO(Units.GRAM));
942        SYMBOL_TO_UNIT.put("μg", MICRO(Units.GRAM));
943        SYMBOL_TO_UNIT.put(MU + "g", MICRO(Units.GRAM));
944    }
945    
946    /**
947     * Holds the standard unit format.
948     */
949    private static final DefaultFormat DEFAULT = initDefaultFormat(new DefaultFormat());
950
951    /**
952     * Holds the ASCIIFormat unit format.
953     */
954    private static final ASCIIFormat ASCII = initASCIIFormat(new ASCIIFormat());
955    
956    // -- FACTORIES
957    
958    protected static DefaultFormat initDefaultFormat(final DefaultFormat defaultFormat) {
959    
960        for (int i = 0; i < METRIC_UNITS.length; i++) {
961            Unit<?> si = METRIC_UNITS[i];
962            String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si).getSymbol() : ((AlternateUnit<?>) si).getSymbol();
963            defaultFormat.label(si, symbol);
964            for (int j = 0; j < METRIC_PREFIX_SYMBOLS.length; j++) {
965                Unit<?> u = si.prefix(MetricPrefix.values()[j]);
966                defaultFormat.label(u, METRIC_PREFIX_SYMBOLS[j] + symbol);
967                if ( "µ".equals(METRIC_PREFIX_SYMBOLS[j]) ) {
968                    defaultFormat.label(u, MU + symbol);
969                }
970            } // TODO what about BINARY_PREFIX here?
971        }
972
973        // -- GRAM/KILOGRAM
974
975        defaultFormat.label(Units.GRAM, "g");
976        for(MetricPrefix prefix : MetricPrefix.values()) {
977            switch (prefix) {
978            case KILO:
979                defaultFormat.label(Units.KILOGRAM, "kg");
980                break;
981            case MICRO:
982                defaultFormat.label(Units.GRAM.prefix(prefix), prefix.getSymbol()+"g");
983                break;
984            default:
985                defaultFormat.label(Units.GRAM.prefix(prefix), prefix.getSymbol()+"g");
986                break;
987            }
988        }
989
990        defaultFormat.label(MICRO(Units.GRAM), MetricPrefix.MICRO.getSymbol() + "g");
991
992        // Alias and ASCIIFormat for Ohm
993        defaultFormat.alias(Units.OHM, "Ohm");
994        for (int i = 0; i < METRIC_PREFIX_SYMBOLS.length; i++) {
995            defaultFormat.alias(Units.OHM.prefix(MetricPrefix.values()[i]), METRIC_PREFIX_SYMBOLS[i] + "Ohm");
996        }
997
998        // Special case for DEGREE_CELSIUS.
999        defaultFormat.label(Units.CELSIUS, "℃");
1000        defaultFormat.alias(Units.CELSIUS, "°C");
1001        for (int i = 0; i < METRIC_PREFIX_SYMBOLS.length; i++) {
1002            defaultFormat.label(Units.CELSIUS.prefix(MetricPrefix.values()[i]), METRIC_PREFIX_SYMBOLS[i] + "℃");
1003            defaultFormat.alias(Units.CELSIUS.prefix(MetricPrefix.values()[i]), METRIC_PREFIX_SYMBOLS[i] + "°C");
1004        }
1005
1006        defaultFormat.label(Units.PERCENT, "%");
1007        defaultFormat.label(Units.METRE, "m");
1008        defaultFormat.label(Units.SECOND, "s");
1009        defaultFormat.label(Units.MINUTE, "min");
1010        defaultFormat.label(Units.HOUR, "h");
1011        defaultFormat.label(Units.DAY, "day");
1012        defaultFormat.alias(Units.DAY, "d");
1013        defaultFormat.label(Units.WEEK, "week");
1014        defaultFormat.label(Units.YEAR, "year");
1015        defaultFormat.alias(Units.YEAR, "days365");
1016        defaultFormat.label(Units.MONTH, "mo");
1017        defaultFormat.alias(Units.MONTH, "mon");
1018        defaultFormat.alias(Units.MONTH, "month");
1019        defaultFormat.label(Units.KILOMETRE_PER_HOUR, "km/h");
1020        defaultFormat.label(Units.CUBIC_METRE, "\u33A5");
1021
1022        // -- LITRE
1023
1024        defaultFormat.label(Units.LITRE, "l");
1025        for(Prefix prefix : MetricPrefix.values()) {
1026            defaultFormat.label(Units.LITRE.prefix(prefix), prefix.getSymbol()+"l");
1027        }   
1028        defaultFormat.label(Units.NEWTON, "N");
1029        defaultFormat.label(Units.RADIAN, "rad");
1030
1031        defaultFormat.label(AbstractUnit.ONE, "one");
1032        
1033        defaultFormat.alias(Units.SQUARE_METRE, "m2");
1034        defaultFormat.alias(Units.CUBIC_METRE, "m3");
1035        
1036        return defaultFormat;
1037    }
1038    
1039    protected static ASCIIFormat initASCIIFormat(final ASCIIFormat asciiFormat) {
1040        
1041        for (int i = 0; i < METRIC_UNITS.length; i++) {
1042            Unit<?> si = METRIC_UNITS[i];
1043            String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si).getSymbol() : ((AlternateUnit<?>) si).getSymbol();
1044            if (isAllASCII(symbol))
1045                asciiFormat.label(si, symbol);
1046            for (int j = 0; j < METRIC_PREFIX_SYMBOLS.length; j++) {
1047                Unit<?> u = si.prefix(MetricPrefix.values()[j]);
1048                if ( "µ".equals(METRIC_PREFIX_SYMBOLS[j]) ) {
1049                    asciiFormat.label(u, "micro" + asciiSymbol(symbol));
1050                }
1051            } // TODO what about BINARY_PREFIX here?
1052        }
1053
1054        // -- GRAM/KILOGRAM
1055
1056        asciiFormat.label(Units.GRAM, "g");
1057        for(MetricPrefix prefix : MetricPrefix.values()) {
1058            switch (prefix) {
1059            case KILO:
1060                asciiFormat.label(Units.KILOGRAM, "kg");
1061                break;
1062            case MICRO:
1063                asciiFormat.label(MICRO(Units.LITRE), "microg"); // instead of 'µg' -> 'microg'
1064                break;
1065            default:
1066                asciiFormat.label(Units.GRAM.prefix(prefix), prefix.getSymbol()+"g");
1067                break;
1068            }
1069        }
1070
1071        // Alias and ASCIIFormat for Ohm
1072        asciiFormat.label(Units.OHM, "Ohm");
1073        for (int i = 0; i < METRIC_PREFIX_SYMBOLS.length; i++) {
1074            asciiFormat.label(Units.OHM.prefix(MetricPrefix.values()[i]), asciiPrefix(METRIC_PREFIX_SYMBOLS[i]) + "Ohm");
1075        }
1076
1077        // Special case for DEGREE_CELSIUS.
1078        asciiFormat.label(Units.CELSIUS, "Celsius");
1079        for (int i = 0; i < METRIC_PREFIX_SYMBOLS.length; i++) {
1080            asciiFormat.label(Units.CELSIUS.prefix(MetricPrefix.values()[i]), asciiPrefix(METRIC_PREFIX_SYMBOLS[i]) + "Celsius");
1081        }
1082        asciiFormat.alias(Units.CELSIUS, "Cel");
1083
1084        asciiFormat.label(Units.METRE, "m");
1085        asciiFormat.label(Units.SECOND, "s");
1086        asciiFormat.label(Units.KILOMETRE_PER_HOUR, "km/h");        
1087        asciiFormat.alias(Units.SQUARE_METRE, "m2");
1088        asciiFormat.alias(Units.CUBIC_METRE, "m3");
1089        
1090        // -- LITRE
1091
1092        asciiFormat.label(Units.LITRE, "l");
1093        for(Prefix prefix : MetricPrefix.values()) {
1094            if(prefix==MICRO) {
1095                asciiFormat.label(MICRO(Units.LITRE), "microL"); // instead of 'µL' -> 'microL'
1096            } else {
1097                asciiFormat.label(Units.LITRE.prefix(prefix), prefix.getSymbol()+"L");
1098            }
1099        }   
1100        asciiFormat.label(Units.NEWTON, "N");
1101        asciiFormat.label(Units.RADIAN, "rad");
1102
1103        asciiFormat.label(AbstractUnit.ONE, "one");
1104        
1105        return asciiFormat;
1106    }
1107}