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