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