001/*
002 * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences.
003 * Copyright (C) 2006 - JScience (http://jscience.org/)
004 * All rights reserved.
005 * 
006 * Permission to use, copy, modify, and distribute this software is
007 * freely granted, provided that this notice is preserved.
008 */
009package javax.measure.unit;
010
011import java.io.IOException;
012import java.lang.CharSequence;
013import java.text.FieldPosition;
014import java.text.Format;
015import java.text.ParseException;
016import java.text.ParsePosition;
017import java.util.HashMap;
018import java.util.Locale;
019//@RETROWEAVER import javolution.text.Appendable;
020import javax.measure.converter.AddConverter;
021import javax.measure.converter.MultiplyConverter;
022import javax.measure.converter.RationalConverter;
023import javax.measure.converter.UnitConverter;
024import javax.measure.quantity.Quantity;
025
026import static javax.measure.unit.SI.*;
027
028/**
029 * <p> This class provides the interface for formatting and parsing {@link 
030 *     Unit units}.</p>
031 *     
032 * <p> For all {@link SI} units, the 20 SI prefixes used to form decimal
033 *     multiples and sub-multiples of SI units are recognized.
034 *     {@link NonSI} units are directly recognized. For example:[code]
035 *        Unit.valueOf("m°C").equals(SI.MILLI(SI.CELSIUS))
036 *        Unit.valueOf("kW").equals(SI.KILO(SI.WATT))
037 *        Unit.valueOf("ft").equals(SI.METER.multiply(0.3048))[/code]</p>
038 *
039 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
040 * @author Eric Russell
041 * @version 1.3, August 29, 2006
042 */
043public abstract class UnitFormat extends Format {
044
045    /**
046     * Holds the standard unit format.
047     */
048    private static final DefaultFormat DEFAULT = new DefaultFormat();
049
050    /**
051     * Holds the ASCIIFormat unit format.
052     */
053    private static final ASCIIFormat ASCII = new ASCIIFormat();
054
055    /**
056     * Returns the unit format for the default locale (format used by 
057     * {@link Unit#valueOf(CharSequence) Unit.valueOf(CharSequence)} and 
058     * {@link Unit#toString() Unit.toString()}).
059     * 
060     *  @return the default unit format (locale sensitive).
061     */
062    public static UnitFormat getInstance() {
063        return UnitFormat.getInstance(Locale.getDefault());
064    }
065
066    /**
067     * Returns the unit format for the specified locale.
068     * 
069     * @return the unit format for the specified locale.
070     */
071    public static UnitFormat getInstance(Locale inLocale) {
072        return DEFAULT; // TBD: Implement Locale Format. 
073    }
074
075    /**
076     * Returns the <a href="http://aurora.regenstrief.org/UCUM/ucum.html">UCUM
077     * </a> international unit format; this format uses characters range
078     * <code>0000-007F</code> exclusively and <b>is not</b> locale-sensitive.
079     * For example: <code>kg.m/s2</code>
080     * 
081     * @return the UCUM international format.
082     */
083    public static UnitFormat getUCUMInstance() {
084        return UnitFormat.ASCII; // TBD - Provide UCUM implementation.
085    }
086  
087    /**
088     * Base constructor.
089     */
090    protected UnitFormat() {
091    }
092
093    /**
094     * Formats the specified unit.
095     *
096     * @param unit the unit to format.
097     * @param appendable the appendable destination.
098     * @throws IOException if an error occurs.
099     */
100    public abstract Appendable format(Unit<?> unit, Appendable appendable)
101            throws IOException;
102
103    /**
104     * Parses a sequence of character to produce a unit or a rational product
105     * of unit. 
106     *
107     * @param csq the <code>CharSequence</code> to parse.
108     * @param pos an object holding the parsing index and error position.
109     * @return an {@link Unit} parsed from the character sequence.
110     * @throws IllegalArgumentException if the character sequence contains
111     *         an illegal syntax.
112     */
113    public abstract Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos)
114            throws ParseException;
115
116    /**
117     * Parses a sequence of character to produce a single unit. 
118     *
119     * @param csq the <code>CharSequence</code> to parse.
120     * @param pos an object holding the parsing index and error position.
121     * @return an {@link Unit} parsed from the character sequence.
122     * @throws IllegalArgumentException if the character sequence does not contain 
123     *         a valid unit identifier.
124     */
125    public abstract Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos)
126            throws ParseException;
127
128    /**
129     * Attaches a system-wide label to the specified unit. For example:
130     * [code]
131     *     UnitFormat.getInstance().label(DAY.multiply(365), "year");
132     *     UnitFormat.getInstance().label(METER.multiply(0.3048), "ft");
133     * [/code]
134     * If the specified label is already associated to an unit the previous 
135     * association is discarded or ignored.
136     *  
137     * @param  unit the unit being labelled. 
138     * @param  label the new label for this unit.
139     * @throws IllegalArgumentException if the label is not a 
140     *         {@link UnitFormat#isValidIdentifier(String)} valid identifier.
141     */
142    public abstract void label(Unit<?> unit, String label);
143
144    /**
145     * Attaches a system-wide alias to this unit. Multiple aliases may
146     * be attached to the same unit. Aliases are used during parsing to
147     * recognize different variants of the same unit. For example:
148     * [code]
149     *     UnitFormat.getLocaleInstance().alias(METER.multiply(0.3048), "foot");
150     *     UnitFormat.getLocaleInstance().alias(METER.multiply(0.3048), "feet");
151     *     UnitFormat.getLocaleInstance().alias(METER, "meter");
152     *     UnitFormat.getLocaleInstance().alias(METER, "metre");
153     * [/code]
154     * If the specified label is already associated to an unit the previous 
155     * association is discarded or ignored.
156     *
157     * @param  unit the unit being aliased.
158     * @param  alias the alias attached to this unit.
159     * @throws IllegalArgumentException if the label is not a 
160     *         {@link UnitFormat#isValidIdentifier(String)} valid identifier.
161     */
162    public abstract void alias(Unit<?> unit, String alias);
163
164    /**
165     * Indicates if the specified name can be used as unit identifier.
166     *
167     * @param  name the identifier to be tested.
168     * @return <code>true</code> if the name specified can be used as 
169     *         label or alias for this format;<code>false</code> otherwise.
170     */
171    public abstract boolean isValidIdentifier(String name);
172
173    /**
174     * Formats an unit and appends the resulting text to a given string
175     * buffer (implements <code>java.text.Format</code>).
176     *
177     * @param unit the unit to format.
178     * @param toAppendTo where the text is to be appended
179     * @param pos the field position (not used).
180     * @return <code>toAppendTo</code>
181     */
182    public final StringBuffer format(Object unit, final StringBuffer toAppendTo,
183            FieldPosition pos) {
184        try {
185            Object dest = toAppendTo;
186            if (dest instanceof Appendable) { 
187                format((Unit<?>) unit, (Appendable)dest);                        
188            } else {  // When retroweaver is used to produce 1.4 binaries.
189                format((Unit<?>) unit, new Appendable() {
190
191                    public Appendable append(char arg0) throws IOException {
192                        toAppendTo.append(arg0);
193                        return null;
194                    }
195
196                    public Appendable append(CharSequence arg0) throws IOException {
197                        toAppendTo.append(arg0);
198                        return null;
199                    }
200
201                    public Appendable append(CharSequence arg0, int arg1, int arg2) throws IOException {
202                        toAppendTo.append(arg0.subSequence(arg1, arg2));
203                        return null;
204                    }});
205            }
206            return toAppendTo;
207        } catch (IOException e) {
208            throw new Error(e); // Should never happen.
209        }
210    }
211
212    /**
213     * Parses the text from a string to produce an object
214     * (implements <code>java.text.Format</code>).
215     * 
216     * @param source the string source, part of which should be parsed.
217     * @param pos the cursor position.
218     * @return the corresponding unit or <code>null</code> if the string 
219     *         cannot be parsed.
220     */
221    public final Unit<?> parseObject(String source, ParsePosition pos) {
222        int start = pos.getIndex();
223        try {
224            return parseProductUnit(source, pos);
225        } catch (ParseException e) {
226            pos.setIndex(start);
227            pos.setErrorIndex(e.getErrorOffset());
228            return null;
229        }
230    }
231    
232    
233    /**
234     * This class represents an exponent with both a power (numerator)
235     * and a root (denominator).
236     */
237    private static class Exponent {
238        public final int pow;
239        public final int root;
240        public Exponent (int pow, int root) {
241            this.pow = pow;
242            this.root = root;
243        }
244    }
245    
246    /**
247     * This class represents the standard format.
248     */
249    protected static class DefaultFormat extends UnitFormat {
250
251        /**
252         * Holds the name to unit mapping.
253         */
254        final HashMap<String, Unit<?>> _nameToUnit = new HashMap<String, Unit<?>>();
255
256        /**
257         * Holds the unit to name mapping.
258         */
259        final HashMap<Unit<?>, String> _unitToName = new HashMap<Unit<?>, String>();
260
261        @Override
262        public void label(Unit<?> unit, String label) {
263            if (!isValidIdentifier(label))
264                throw new IllegalArgumentException("Label: " + label
265                        + " is not a valid identifier.");
266            synchronized (this) {
267                _nameToUnit.put(label, unit);
268                _unitToName.put(unit, label);
269            }
270        }
271
272        @Override
273        public void alias(Unit<?> unit, String alias) {
274            if (!isValidIdentifier(alias))
275                throw new IllegalArgumentException("Alias: " + alias
276                        + " is not a valid identifier.");
277            synchronized (this) {
278                _nameToUnit.put(alias, unit);
279            }
280        }
281
282        @Override
283        public boolean isValidIdentifier(String name) {
284            if ((name == null) || (name.length() == 0))
285                return false;
286            for (int i = 0; i < name.length(); i++) {
287                if (!isUnitIdentifierPart(name.charAt(i)))
288                    return false;
289            }
290            return true;
291        }
292
293        static boolean isUnitIdentifierPart(char ch) {
294            return Character.isLetter(ch) || 
295               (!Character.isWhitespace(ch) && !Character.isDigit(ch)
296                  && (ch != '·') && (ch != '*') && (ch != '/')
297                  && (ch != '(') && (ch != ')') && (ch != '[') && (ch != ']')    
298                  && (ch != '¹') && (ch != '²') && (ch != '³') 
299                  && (ch != '^') && (ch != '+') && (ch != '-'));
300        }
301        
302        // Returns the name for the specified unit or null if product unit.
303        public String nameFor(Unit<?> unit) {
304            // Searches label database.
305            String label = _unitToName.get(unit);
306            if (label != null)
307                return label;
308            if (unit instanceof BaseUnit)
309                return ((BaseUnit<?>) unit).getSymbol();
310            if (unit instanceof AlternateUnit)
311                return ((AlternateUnit<?>) unit).getSymbol();
312            if (unit instanceof TransformedUnit) {
313                TransformedUnit<?> tfmUnit = (TransformedUnit<?>) unit;
314                Unit<?> baseUnits = tfmUnit.getStandardUnit();
315                UnitConverter cvtr = tfmUnit.toStandardUnit();
316                StringBuffer result = new StringBuffer();
317                String baseUnitName = baseUnits.toString();
318                if ((baseUnitName.indexOf('·') >= 0) ||
319                    (baseUnitName.indexOf('*') >= 0) ||
320                    (baseUnitName.indexOf('/') >= 0)) {
321                    // We could use parentheses whenever baseUnits is an
322                    // instanceof ProductUnit, but most ProductUnits have aliases,
323                    // so we'd end up with a lot of unnecessary parentheses.
324                    result.append('(');
325                    result.append(baseUnitName);
326                    result.append(')');
327                } else {
328                    result.append(baseUnitName);
329                }
330                if (cvtr instanceof AddConverter) {
331                    result.append('+');
332                    result.append(((AddConverter) cvtr).getOffset());
333                } else if (cvtr instanceof RationalConverter) {
334                    long dividend = ((RationalConverter) cvtr).getDividend();
335                    if (dividend != 1) {
336                        result.append('*');
337                        result.append(dividend);
338                    }
339                    long divisor = ((RationalConverter) cvtr).getDivisor();
340                    if (divisor != 1) {
341                        result.append('/');
342                        result.append(divisor);
343                    }          ;
344                } else if (cvtr instanceof MultiplyConverter) {
345                    result.append('*');
346                    result.append(((MultiplyConverter) cvtr).getFactor());
347                } else { // Other converters.
348                    return "[" + baseUnits + "?]";
349                }
350                return result.toString();
351            }
352            // Compound unit.
353            if (unit instanceof CompoundUnit) {
354                CompoundUnit<?> cpdUnit = (CompoundUnit<?>) unit;
355                return nameFor(cpdUnit.getHigher()).toString() + ":"
356                        + nameFor(cpdUnit.getLower());
357            }
358            return null; // Product unit.
359        }
360
361        // Returns the unit for the specified name.
362        public Unit<?> unitFor(String name) {
363            Unit<?> unit = _nameToUnit.get(name);
364            if (unit != null)
365                return unit;
366            unit = Unit.SYMBOL_TO_UNIT.get(name);
367            return unit;
368        }
369
370        ////////////////////////////
371        // Parsing.
372        
373        @SuppressWarnings("unchecked")
374        public Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) 
375                throws ParseException {
376            int startIndex = pos.getIndex();
377            String name = readIdentifier(csq, pos);
378            Unit unit = unitFor(name);
379            check(unit != null, name + " not recognized", csq, startIndex);
380            return unit;            
381        }
382        
383        @SuppressWarnings("unchecked")
384        @Override
385        public Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) 
386                throws ParseException {
387            Unit result = Unit.ONE;
388            int token = nextToken(csq, pos);
389            switch (token) {
390            case IDENTIFIER:
391                result = parseSingleUnit(csq, pos);
392                break;
393            case OPEN_PAREN:
394                pos.setIndex(pos.getIndex() + 1);
395                result = parseProductUnit(csq, pos);
396                token = nextToken(csq, pos);
397                check(token == CLOSE_PAREN, "')' expected", csq, pos.getIndex());
398                pos.setIndex(pos.getIndex() + 1);
399                break;
400            }
401            token = nextToken(csq, pos);
402            while (true) {
403                switch (token) {
404                case EXPONENT:
405                    Exponent e = readExponent(csq, pos);
406                    if (e.pow != 1) {
407                        result = result.pow(e.pow);
408                    }
409                    if (e.root != 1) {
410                        result = result.root(e.root);
411                    }   
412                    break;
413                case MULTIPLY:
414                    pos.setIndex(pos.getIndex() + 1);
415                    token = nextToken(csq, pos);
416                    if (token == INTEGER) {
417                        long n = readLong(csq, pos);
418                        if (n != 1) {
419                            result = result.times(n);
420                        }
421                    } else if (token == FLOAT) {
422                        double d = readDouble(csq, pos);
423                        if (d != 1.0) {
424                            result = result.times(d);
425                        }
426                    } else {
427                        result = result.times(parseProductUnit(csq, pos));
428                    }
429                    break;
430                case DIVIDE:
431                    pos.setIndex(pos.getIndex() + 1);
432                    token = nextToken(csq, pos);
433                    if (token == INTEGER) {
434                        long n = readLong(csq, pos);
435                        if (n != 1) {
436                            result = result.divide(n);
437                        }
438                    } else if (token == FLOAT) {
439                        double d = readDouble(csq, pos);
440                        if (d != 1.0) {
441                            result = result.divide(d);
442                        }
443                    } else {
444                        result = result.divide(parseProductUnit(csq, pos));
445                    }
446                    break;
447                case PLUS:
448                    pos.setIndex(pos.getIndex() + 1);
449                    token = nextToken(csq, pos);
450                    if (token == INTEGER) {
451                        long n = readLong(csq, pos);
452                        if (n != 1) {
453                            result = result.plus(n);
454                        }
455                    } else if (token == FLOAT) {
456                        double d = readDouble(csq, pos);
457                        if (d != 1.0) {
458                            result = result.plus(d);
459                        }
460                    } else {
461                        throw new ParseException("not a number", pos.getIndex());
462                    }
463                    break;
464                case EOF:
465                case CLOSE_PAREN:
466                    return result;
467                default:
468                    throw new ParseException("unexpected token " + token, pos.getIndex());
469                }
470                token = nextToken(csq, pos);
471            }
472        }
473
474        private static final int EOF = 0;
475        private static final int IDENTIFIER = 1;
476        private static final int OPEN_PAREN= 2;
477        private static final int CLOSE_PAREN= 3;
478        private static final int EXPONENT = 4;
479        private static final int MULTIPLY = 5;
480        private static final int DIVIDE = 6;
481        private static final int PLUS = 7;
482        private static final int INTEGER = 8;
483        private static final int FLOAT = 9;
484
485        private int nextToken(CharSequence csq, ParsePosition pos) {
486            final int length = csq.length();
487            while (pos.getIndex() < length) {
488                char c = csq.charAt(pos.getIndex());
489                if (isUnitIdentifierPart(c)) {
490                    return IDENTIFIER;
491                } else if (c == '(') {
492                    return OPEN_PAREN;
493                } else if (c == ')') {
494                    return CLOSE_PAREN;
495                } else if ((c == '^') || (c == '¹') || (c == '²') || (c == '³')) {
496                    return EXPONENT;
497                } else if (c == '*') {
498                    char c2 = csq.charAt(pos.getIndex() + 1);
499                    if (c2 == '*') {
500                        return EXPONENT;
501                    } else {
502                        return MULTIPLY;
503                    }
504                } else if (c == '·') {
505                    return MULTIPLY;
506                } else if (c == '/') {
507                    return DIVIDE;
508                } else if (c == '+') {
509                    return PLUS;
510                } else if ((c == '-') || Character.isDigit(c)) {
511                    int index = pos.getIndex()+1;
512                    while ((index < length) && 
513                           (Character.isDigit(c) || (c == '-') || (c == '.') || (c == 'E'))) {
514                        c = csq.charAt(index++);
515                        if (c == '.') {
516                            return FLOAT;
517                        }
518                    }
519                    return INTEGER;
520                }
521                pos.setIndex(pos.getIndex() + 1);
522            }
523            return EOF;
524        }
525
526        private void check(boolean expr, String message, CharSequence csq,
527                int index) throws ParseException {
528            if (!expr) {
529                throw new ParseException(message + " (in " + csq
530                        + " at index " + index + ")", index);
531            }
532        }
533        
534        private Exponent readExponent (CharSequence csq, ParsePosition pos) {
535            char c = csq.charAt(pos.getIndex());
536            if (c == '^') {
537                pos.setIndex(pos.getIndex()+1);
538            } else if (c == '*') {
539                pos.setIndex(pos.getIndex()+2);
540            }
541            final int length = csq.length();
542            int pow = 0;
543            boolean isPowNegative = false;
544            int root = 0;
545            boolean isRootNegative = false;
546            boolean isRoot = false;
547            while (pos.getIndex() < length) {
548                c = csq.charAt(pos.getIndex());
549                if (c == '¹') {
550                    if (isRoot) {
551                        root = root * 10 + 1;
552                    } else {
553                        pow = pow * 10 + 1;
554                    }
555                } else if (c == '²') {
556                    if (isRoot) {
557                        root = root * 10 + 2;
558                    } else {
559                        pow = pow * 10 + 2;
560                    }
561                } else if (c == '³') {
562                    if (isRoot) {
563                        root = root * 10 + 3;
564                    } else {
565                        pow = pow * 10 + 3;
566                    }
567                } else if (c == '-') {
568                    if (isRoot) {
569                        isRootNegative = true;
570                    } else {
571                        isPowNegative = true;
572                    }
573                } else if ((c >= '0') && (c <= '9')) {
574                    if (isRoot) {
575                        root = root * 10 + (c - '0');
576                    } else {
577                        pow = pow * 10 + (c - '0');
578                    }
579                } else if (c == ':') {
580                    isRoot = true;
581                } else {
582                    break;
583                }
584                pos.setIndex(pos.getIndex()+1);
585            }
586            if (pow == 0) pow = 1;
587            if (root == 0) root = 1;
588            return new Exponent(isPowNegative ? -pow : pow, 
589                              isRootNegative ? -root : root);
590        }
591
592        private long readLong (CharSequence csq, ParsePosition pos) {
593            final int length = csq.length();
594            int result = 0;
595            boolean isNegative = false;
596            while (pos.getIndex() < length) {
597                char c = csq.charAt(pos.getIndex());
598                if (c == '-') {
599                    isNegative = true;
600                } else if ((c >= '0') && (c <= '9')) {
601                    result = result * 10 + (c - '0');
602                } else {
603                    break;
604                }
605                pos.setIndex(pos.getIndex()+1);
606            }
607            return isNegative ? -result : result;
608        }
609        
610        private double readDouble (CharSequence csq, ParsePosition pos) {
611            final int length = csq.length();
612            int start = pos.getIndex();
613            int end = start+1;
614            while (end < length) {
615                if ("012356789+-.E".indexOf(csq.charAt(end)) < 0) {
616                    break;
617                }
618                end += 1;
619            }
620            pos.setIndex(end+1);
621            return Double.parseDouble(csq.subSequence(start,end).toString());
622        }
623        
624        private String readIdentifier(CharSequence csq, ParsePosition pos) {
625            final int length = csq.length();
626            int start = pos.getIndex();
627            int i = start;
628            while ((++i < length) && isUnitIdentifierPart(csq.charAt(i))) { }
629            pos.setIndex(i);
630            return csq.subSequence(start, i).toString();
631        }
632        
633        ////////////////////////////
634        // Formatting.
635
636        @Override
637        public Appendable format(Unit<?> unit, Appendable appendable)
638                throws IOException {
639            String name = nameFor(unit);
640            if (name != null)
641                return appendable.append(name);
642            if (!(unit instanceof ProductUnit))
643                throw new IllegalArgumentException("Cannot format given Object as a Unit");
644
645            // Product unit.
646            ProductUnit<?> productUnit = (ProductUnit<?>) unit;
647            int invNbr = 0;
648
649            // Write positive exponents first.
650            boolean start = true;
651            for (int i = 0; i < productUnit.getUnitCount(); i++) {
652                int pow = productUnit.getUnitPow(i);
653                if (pow >= 0) {
654                    if (!start) {
655                        appendable.append('·'); // Separator.
656                    }
657                    name = nameFor(productUnit.getUnit(i));
658                    int root = productUnit.getUnitRoot(i);
659                    append(appendable, name, pow, root);
660                    start = false;
661                } else {
662                    invNbr++;
663                }
664            }
665
666            // Write negative exponents.
667            if (invNbr != 0) {
668                if (start) {
669                    appendable.append('1'); // e.g. 1/s
670                }
671                appendable.append('/');
672                if (invNbr > 1) {
673                    appendable.append('(');
674                }
675                start = true;
676                for (int i = 0; i < productUnit.getUnitCount(); i++) {
677                    int pow = productUnit.getUnitPow(i);
678                    if (pow < 0) {
679                        name = nameFor(productUnit.getUnit(i));
680                        int root = productUnit.getUnitRoot(i);
681                        if (!start) {
682                            appendable.append('·'); // Separator.
683                        }
684                        append(appendable, name, -pow, root);
685                        start = false;
686                    }
687                }
688                if (invNbr > 1) {
689                    appendable.append(')');
690                }
691            }
692            return appendable;
693        }
694
695        private void append(Appendable appendable, CharSequence symbol,
696                int pow, int root) throws IOException {
697            appendable.append(symbol);
698            if ((pow != 1) || (root != 1)) {
699                // Write exponent.
700                if ((pow == 2) && (root == 1)) {
701                    appendable.append('²'); // Square
702                } else if ((pow == 3) && (root == 1)) {
703                    appendable.append('³'); // Cubic
704                } else {
705                    // Use general exponent form.
706                    appendable.append('^');
707                    appendable.append(String.valueOf(pow));
708                    if (root != 1) {
709                        appendable.append(':');
710                        appendable.append(String.valueOf(root));
711                    }
712                }
713            }
714        }
715        
716        private static final long serialVersionUID = 1L;
717    }
718
719    /**
720     * This class represents the ASCIIFormat format.
721     */
722    protected static class ASCIIFormat extends DefaultFormat {
723
724        @Override
725        public String nameFor(Unit<?> unit) {
726            // First search if specific ASCII name should be used.
727            String name = _unitToName.get(unit);
728            if (name != null)
729                return name;
730            // Else returns default name.
731            return DEFAULT.nameFor(unit);
732        }
733
734        @Override
735        public Unit<?> unitFor(String name) {
736            // First search if specific ASCII name.
737            Unit<?> unit = _nameToUnit.get(name);
738            if (unit != null)
739                return unit;
740            // Else returns default mapping.
741            return DEFAULT.unitFor(name);
742        }
743
744        @Override
745        public Appendable format(Unit<?> unit, Appendable appendable)
746                throws IOException {
747            String name = nameFor(unit);
748            if (name != null)
749                return appendable.append(name);
750            if (!(unit instanceof ProductUnit))
751                throw new IllegalArgumentException(
752                        "Cannot format given Object as a Unit");
753
754            ProductUnit<?> productUnit = (ProductUnit<?>) unit;
755            for (int i = 0; i < productUnit.getUnitCount(); i++) {
756                if (i != 0) {
757                    appendable.append('*'); // Separator.
758                }
759                name = nameFor(productUnit.getUnit(i));
760                int pow = productUnit.getUnitPow(i);
761                int root = productUnit.getUnitRoot(i);
762                appendable.append(name);
763                if ((pow != 1) || (root != 1)) {
764                    // Use general exponent form.
765                    appendable.append('^');
766                    appendable.append(String.valueOf(pow));
767                    if (root != 1) {
768                        appendable.append(':');
769                        appendable.append(String.valueOf(root));
770                    }
771                }
772            }
773            return appendable;
774        }
775
776        private static final long serialVersionUID = 1L;
777    }
778
779
780    ////////////////////////////////////////////////////////////////////////////
781    // Initializes the standard unit database for SI units.
782    
783    private static final Unit<?>[] SI_UNITS = { SI.AMPERE, SI.BECQUEREL,
784            SI.CANDELA, SI.COULOMB, SI.FARAD, SI.GRAY, SI.HENRY, SI.HERTZ,
785            SI.JOULE, SI.KATAL, SI.KELVIN, SI.LUMEN, SI.LUX, SI.METRE, SI.MOLE,
786            SI.NEWTON, SI.OHM, SI.PASCAL, SI.RADIAN, SI.SECOND, SI.SIEMENS,
787            SI.SIEVERT, SI.STERADIAN, SI.TESLA, SI.VOLT, SI.WATT, SI.WEBER };
788
789    private static final String[] PREFIXES = { "Y", "Z", "E", "P", "T", "G",
790            "M", "k", "h", "da", "d", "c", "m", "µ", "n", "p", "f", "a", "z",
791            "y" };
792
793    private static final UnitConverter[] CONVERTERS = { E24, E21, E18, E15, E12,
794            E9, E6, E3, E2, E1, Em1, Em2, Em3, Em6, Em9, Em12,
795            Em15, Em18, Em21, Em24 };
796    
797    private static String asciiPrefix(String prefix) {
798        return prefix == "µ" ? "micro" : prefix;
799    }
800    
801    static {
802        for (int i = 0; i < SI_UNITS.length; i++) {
803            for (int j = 0; j < PREFIXES.length; j++) {
804                Unit<?> si = SI_UNITS[i];
805                Unit<?> u = si.transform(CONVERTERS[j]);
806                String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si)
807                        .getSymbol() : ((AlternateUnit<?>) si).getSymbol();
808                DEFAULT.label(u, PREFIXES[j] + symbol);
809                if (PREFIXES[j] == "µ") {
810                    ASCII.label(u, "micro" + symbol);
811                }
812            }
813        }
814        // Special case for KILOGRAM.
815        DEFAULT.label(SI.GRAM, "g");
816        for (int i = 0; i < PREFIXES.length; i++) {
817            if (CONVERTERS[i] == E3) continue;  // kg is already defined.
818            DEFAULT.label(SI.KILOGRAM.transform(CONVERTERS[i].concatenate(Em3)),
819                        PREFIXES[i] + "g");
820            if (PREFIXES[i] == "µ") {
821                 ASCII.label(SI.KILOGRAM.transform(CONVERTERS[i].concatenate(Em3)), "microg");
822            }   
823        }
824
825        // Alias and ASCIIFormat for Ohm
826        DEFAULT.alias(SI.OHM, "Ohm");
827        ASCII.label(SI.OHM, "Ohm");
828        for (int i = 0; i < PREFIXES.length; i++) {
829            DEFAULT.alias(SI.OHM.transform(CONVERTERS[i]), PREFIXES[i] + "Ohm");
830            ASCII.label(SI.OHM.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Ohm");
831        }
832        
833        // Special case for DEGREE_CElSIUS.
834        DEFAULT.label(SI.CELSIUS, "℃");
835        DEFAULT.alias(SI.CELSIUS, "°C");
836        ASCII.label(SI.CELSIUS, "Celsius");
837        for (int i = 0; i < PREFIXES.length; i++) {
838            DEFAULT.label(SI.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "℃");
839            DEFAULT.alias(SI.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "°C");
840            ASCII.label(SI.CELSIUS.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Celsius");
841        }
842    }
843    
844    ////////////////////////////////////////////////////////////////////////////
845    // To be moved in resource bundle in future release (locale dependent). 
846    static {
847        DEFAULT.label(NonSI.PERCENT, "%");
848        DEFAULT.label(NonSI.DECIBEL, "dB");
849        DEFAULT.label(NonSI.G, "grav");
850        DEFAULT.label(NonSI.ATOM, "atom");
851        DEFAULT.label(NonSI.REVOLUTION, "rev");
852        DEFAULT.label(NonSI.DEGREE_ANGLE, "°");
853        ASCII.label(NonSI.DEGREE_ANGLE, "degree_angle");
854        DEFAULT.label(NonSI.MINUTE_ANGLE, "'");
855        DEFAULT.label(NonSI.SECOND_ANGLE, "\"");
856        DEFAULT.label(NonSI.CENTIRADIAN, "centiradian");
857        DEFAULT.label(NonSI.GRADE, "grade");
858        DEFAULT.label(NonSI.ARE, "a");
859        DEFAULT.label(NonSI.HECTARE, "ha");
860        DEFAULT.label(NonSI.BYTE, "byte");
861        DEFAULT.label(NonSI.MINUTE, "min");
862        DEFAULT.label(NonSI.HOUR, "h");
863        DEFAULT.label(NonSI.DAY, "day");
864        DEFAULT.label(NonSI.WEEK, "week");
865        DEFAULT.label(NonSI.YEAR, "year");
866        DEFAULT.label(NonSI.MONTH, "month");
867        DEFAULT.label(NonSI.DAY_SIDEREAL, "day_sidereal");
868        DEFAULT.label(NonSI.YEAR_SIDEREAL, "year_sidereal");
869        DEFAULT.label(NonSI.YEAR_CALENDAR, "year_calendar");
870        DEFAULT.label(NonSI.E, "e");
871        DEFAULT.label(NonSI.FARADAY, "Fd");
872        DEFAULT.label(NonSI.FRANKLIN, "Fr");
873        DEFAULT.label(NonSI.GILBERT, "Gi");
874        DEFAULT.label(NonSI.ERG, "erg");
875        DEFAULT.label(NonSI.ELECTRON_VOLT, "eV");
876        DEFAULT.label(SI.KILO(NonSI.ELECTRON_VOLT), "keV");
877        DEFAULT.label(SI.MEGA(NonSI.ELECTRON_VOLT), "MeV");
878        DEFAULT.label(SI.GIGA(NonSI.ELECTRON_VOLT), "GeV");
879        DEFAULT.label(NonSI.LAMBERT, "La");
880        DEFAULT.label(NonSI.FOOT, "ft");
881        DEFAULT.label(NonSI.FOOT_SURVEY_US, "foot_survey_us");
882        DEFAULT.label(NonSI.YARD, "yd");
883        DEFAULT.label(NonSI.INCH, "in");
884        DEFAULT.label(NonSI.MILE, "mi");
885        DEFAULT.label(NonSI.NAUTICAL_MILE, "nmi");
886        DEFAULT.label(NonSI.MILES_PER_HOUR, "mph");
887        DEFAULT.label(NonSI.ANGSTROM, "Å");
888        ASCII.label(NonSI.ANGSTROM, "Angstrom");
889        DEFAULT.label(NonSI.ASTRONOMICAL_UNIT, "ua");
890        DEFAULT.label(NonSI.LIGHT_YEAR, "ly");
891        DEFAULT.label(NonSI.PARSEC, "pc");
892        DEFAULT.label(NonSI.POINT, "pt");
893        DEFAULT.label(NonSI.PIXEL, "pixel");
894        DEFAULT.label(NonSI.MAXWELL, "Mx");
895        DEFAULT.label(NonSI.GAUSS, "G");
896        DEFAULT.label(NonSI.ATOMIC_MASS, "u");
897        DEFAULT.label(NonSI.ELECTRON_MASS, "me");
898        DEFAULT.label(NonSI.POUND, "lb");
899        DEFAULT.label(NonSI.OUNCE, "oz");
900        DEFAULT.label(NonSI.TON_US, "ton_us");
901        DEFAULT.label(NonSI.TON_UK, "ton_uk");
902        DEFAULT.label(NonSI.METRIC_TON, "t");
903        DEFAULT.label(NonSI.DYNE, "dyn");
904        DEFAULT.label(NonSI.KILOGRAM_FORCE, "kgf");
905        DEFAULT.label(NonSI.POUND_FORCE, "lbf");
906        DEFAULT.label(NonSI.HORSEPOWER, "hp");
907        DEFAULT.label(NonSI.ATMOSPHERE, "atm");
908        DEFAULT.label(NonSI.BAR, "bar");
909        DEFAULT.label(NonSI.MILLIMETER_OF_MERCURY, "mmHg");
910        DEFAULT.label(NonSI.INCH_OF_MERCURY, "inHg");
911        DEFAULT.label(NonSI.RAD, "rd");
912        DEFAULT.label(NonSI.REM, "rem");
913        DEFAULT.label(NonSI.CURIE, "Ci");
914        DEFAULT.label(NonSI.RUTHERFORD, "Rd");
915        DEFAULT.label(NonSI.SPHERE, "sphere");
916        DEFAULT.label(NonSI.RANKINE, "°R");
917        ASCII.label(NonSI.RANKINE, "degree_rankine");
918        DEFAULT.label(NonSI.FAHRENHEIT, "°F");
919        ASCII.label(NonSI.FAHRENHEIT, "degree_fahrenheit");
920        DEFAULT.label(NonSI.KNOT, "kn");
921        DEFAULT.label(NonSI.MACH, "Mach");
922        DEFAULT.label(NonSI.C, "c");
923        DEFAULT.label(NonSI.LITRE, "L");
924        DEFAULT.label(SI.MICRO(NonSI.LITRE), "µL");
925        ASCII.label(SI.MICRO(NonSI.LITRE), "microL");
926        DEFAULT.label(SI.MILLI(NonSI.LITRE), "mL");
927        DEFAULT.label(SI.CENTI(NonSI.LITRE), "cL");
928        DEFAULT.label(SI.DECI(NonSI.LITRE), "dL");
929        DEFAULT.label(NonSI.GALLON_LIQUID_US, "gal");
930        DEFAULT.label(NonSI.OUNCE_LIQUID_US, "oz");
931        DEFAULT.label(NonSI.GALLON_DRY_US, "gallon_dry_us");
932        DEFAULT.label(NonSI.GALLON_UK, "gallon_uk");
933        DEFAULT.label(NonSI.OUNCE_LIQUID_UK, "oz_uk");
934        DEFAULT.label(NonSI.ROENTGEN, "Roentgen");
935        if (Locale.getDefault().getCountry().equals("GB")) {
936            DEFAULT.label(NonSI.GALLON_UK, "gal");
937            DEFAULT.label(NonSI.OUNCE_LIQUID_UK, "oz");
938        }
939    }
940}