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