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}