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