001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2016, Jean-Marie Dautelle, Werner Keil, V2COM. 004 * 005 * All rights reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without modification, 008 * are permitted provided that the following conditions are met: 009 * 010 * 1. Redistributions of source code must retain the above copyright notice, 011 * this list of conditions and the following disclaimer. 012 * 013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions 014 * and the following disclaimer in the documentation and/or other materials provided with the distribution. 015 * 016 * 3. Neither the name of JSR-363 nor the names of its contributors may be used to endorse or promote products 017 * derived from this software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package tec.units.ri.format; 031 032import static tec.units.ri.unit.MetricPrefix.*; 033import static tec.units.ri.unit.Units.LITRE; 034 035import java.io.IOException; 036import java.lang.CharSequence; 037import java.util.HashMap; 038import java.util.Map; 039 040import tec.units.ri.AbstractUnit; 041import tec.units.ri.function.AddConverter; 042import tec.units.ri.function.MultiplyConverter; 043import tec.units.ri.function.RationalConverter; 044import tec.units.ri.internal.format.l10n.FieldPosition; 045import tec.units.ri.internal.format.l10n.ParsePosition; 046import tec.units.ri.unit.AlternateUnit; 047import tec.units.ri.unit.BaseUnit; 048import tec.units.ri.unit.ProductUnit; 049import tec.units.ri.unit.TransformedUnit; 050import tec.units.ri.unit.Units; 051import tec.units.ri.unit.MetricPrefix; 052 053import javax.measure.Unit; 054import javax.measure.UnitConverter; 055import javax.measure.Quantity; 056import javax.measure.format.ParserException; 057import javax.measure.format.UnitFormat; 058 059/** 060 * <p> 061 * This class implements the {@link UnitFormat} interface for formatting and parsing {@link Unit units}. 062 * </p> 063 * 064 * <p> 065 * For SI units, the 20 SI prefixes used to form decimal multiples and sub-multiples of SI units are recognized. {@link Units} are directly 066 * recognized. For example:<br> 067 * <code> 068 * AbstractUnit.parse("m°C").equals(MetricPrefix.MILLI(Units.CELSIUS)) 069 * AbstractUnit.parse("kW").equals(MetricPrefix.KILO(Units.WATT)) 070 * AbstractUnit.parse("ft").equals(Units.METRE.multiply(0.3048))</code> 071 * </p> 072 * 073 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> 074 * @author <a href="mailto:units@catmedia.us">Werner Keil</a> 075 * @author Eric Russell 076 * @version 1.0, August 9, 2016 077 */ 078public abstract class SimpleUnitFormat extends AbstractUnitFormat { 079 /** 080 * 081 */ 082 // private static final long serialVersionUID = 4149424034841739785L; 083 084 /** 085 * Flavor of this format 086 * 087 * @author Werner 088 * 089 */ 090 public static enum Flavor { 091 Default, ASCII 092 } 093 094 /** 095 * Holds the standard unit format. 096 */ 097 private static final DefaultFormat DEFAULT = new DefaultFormat(); 098 099 /** 100 * Holds the ASCIIFormat unit format. 101 */ 102 private static final ASCIIFormat ASCII = new ASCIIFormat(); 103 104 /** 105 * Returns the unit format for the default locale (format used by {@link AbstractUnit#parse(CharSequence) AbstractUnit.parse(CharSequence)} and 106 * {@link Unit#toString() Unit.toString()}). 107 * 108 * @return the default unit format (locale sensitive). 109 */ 110 public static SimpleUnitFormat getInstance() { 111 return getInstance(Flavor.Default); 112 } 113 114 /** 115 * Returns the {@link SimpleUnitFormat} in the desired {@link Flavor} 116 * 117 * @return the instance for the given {@link Flavor}. 118 */ 119 public static SimpleUnitFormat getInstance(Flavor flavor) { 120 switch (flavor) { 121 case ASCII: 122 return SimpleUnitFormat.ASCII; 123 default: 124 return DEFAULT; 125 } 126 } 127 128 /** 129 * Base constructor. 130 */ 131 protected SimpleUnitFormat() { 132 } 133 134 /** 135 * Formats the specified unit. 136 * 137 * @param unit 138 * the unit to format. 139 * @param appendable 140 * the appendable destination. 141 * @throws IOException 142 * if an error occurs. 143 */ 144 public abstract Appendable format(Unit<?> unit, Appendable appendable) throws IOException; 145 146 /** 147 * Parses a sequence of character to produce a unit or a rational product of unit. 148 * 149 * @param csq 150 * the <code>CharSequence</code> to parse. 151 * @param pos 152 * an object holding the parsing index and error position. 153 * @return an {@link Unit} parsed from the character sequence. 154 * @throws IllegalArgumentException 155 * if the character sequence contains an illegal syntax. 156 */ 157 @SuppressWarnings("rawtypes") 158 public abstract Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) throws ParserException; 159 160 /** 161 * Parses a sequence of character to produce a single unit. 162 * 163 * @param csq 164 * the <code>CharSequence</code> to parse. 165 * @param pos 166 * an object holding the parsing index and error position. 167 * @return an {@link Unit} parsed from the character sequence. 168 * @throws IllegalArgumentException 169 * if the character sequence does not contain a valid unit identifier. 170 */ 171 @SuppressWarnings("rawtypes") 172 public abstract Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) throws ParserException; 173 174 /** 175 * Attaches a system-wide label to the specified unit. For example:<br> 176 * <code> 177 * SimpleUnitFormat.getInstance().label(DAY.multiply(365), "year"); 178 * SimpleUnitFormat.getInstance().label(METRE.multiply(0.3048), "ft"); </code> <br> 179 * If the specified label is already associated to an unit the previous association is discarded or ignored. 180 * 181 * @param unit 182 * the unit being labeled. 183 * @param label 184 * the new label for this unit. 185 * @throws IllegalArgumentException 186 * if the label is not a {@link SimpleUnitFormat#isValidIdentifier(String)} valid identifier. 187 */ 188 public abstract void label(Unit<?> unit, String label); 189 190 /** 191 * Attaches a system-wide alias to this unit. Multiple aliases may be attached to the same unit. Aliases are used during parsing to recognize 192 * different variants of the same unit. For example: [code] SimpleUnitformat.getInstance().alias(METER.multiply(0.3048), "foot"); 193 * SimpleUnitFormat.getInstance().alias(METER.multiply(0.3048), "feet"); SimpleUnitFormat.getInstance().alias(METER, "meter"); 194 * SimpleUnitFormat.getInstance().alias(METER, "metre"); [/code] If the specified label is already associated to an unit the previous association is 195 * discarded or ignored. 196 * 197 * @param unit 198 * the unit being aliased. 199 * @param alias 200 * the alias attached to this unit. 201 * @throws IllegalArgumentException 202 * if the label is not a {@link SimpleUnitFormat#isValidIdentifier(String)} valid identifier. 203 */ 204 public abstract void alias(Unit<?> unit, String alias); 205 206 /** 207 * Indicates if the specified name can be used as unit identifier. 208 * 209 * @param name 210 * the identifier to be tested. 211 * @return <code>true</code> if the name specified can be used as label or alias for this format;<code>false</code> otherwise. 212 */ 213 public abstract boolean isValidIdentifier(String name); 214 215 /** 216 * Formats an unit and appends the resulting text to a given string buffer. 217 * 218 * @param unit 219 * the unit to format. 220 * @param toAppendTo 221 * where the text is to be appended 222 * @param pos 223 * the field position (not used). 224 * @return <code>toAppendTo</code> 225 */ 226 public final StringBuffer format(Object unit, final StringBuffer toAppendTo, FieldPosition pos) { 227 try { 228 Object dest = toAppendTo; 229 if (dest instanceof Appendable) { 230 format((Unit<?>) unit, (Appendable) dest); 231 } else { // When retroweaver is used to produce 1.4 binaries. 232 format((Unit<?>) unit, new Appendable() { 233 234 public Appendable append(char arg0) throws IOException { 235 toAppendTo.append(arg0); 236 return null; 237 } 238 239 public Appendable append(CharSequence arg0) throws IOException { 240 toAppendTo.append(arg0); 241 return null; 242 } 243 244 public Appendable append(CharSequence arg0, int arg1, int arg2) throws IOException { 245 toAppendTo.append(arg0.subSequence(arg1, arg2)); 246 return null; 247 } 248 }); 249 } 250 return toAppendTo; 251 } catch (IOException e) { 252 throw new Error(e); // Should never happen. 253 } 254 } 255 256 /** 257 * Parses the text from a string to produce an object. 258 * 259 * @param source 260 * the string source, part of which should be parsed. 261 * @param pos 262 * the cursor position. 263 * @return the corresponding unit or <code>null</code> if the string cannot be parsed. 264 */ 265 public final Unit<?> parseObject(String source, ParsePosition pos) throws ParserException { 266 // int start = pos.getIndex(); 267 return parseProductUnit(source, pos); 268 } 269 270 /** 271 * This class represents an exponent with both a power (numerator) and a root (denominator). 272 */ 273 private static class Exponent { 274 public final int pow; 275 public final int root; 276 277 public Exponent(int pow, int root) { 278 this.pow = pow; 279 this.root = root; 280 } 281 } 282 283 /** 284 * This class represents the standard format. 285 */ 286 protected static class DefaultFormat extends SimpleUnitFormat { 287 288 /** 289 * Holds the name to unit mapping. 290 */ 291 final HashMap<String, Unit<?>> _nameToUnit = new HashMap<String, Unit<?>>(); 292 293 /** 294 * Holds the unit to name mapping. 295 */ 296 final HashMap<Unit<?>, String> _unitToName = new HashMap<Unit<?>, String>(); 297 298 @Override 299 public void label(Unit<?> unit, String label) { 300 if (!isValidIdentifier(label)) 301 throw new IllegalArgumentException("Label: " + label + " is not a valid identifier."); 302 synchronized (this) { 303 _nameToUnit.put(label, unit); 304 _unitToName.put(unit, label); 305 } 306 } 307 308 @Override 309 public void alias(Unit<?> unit, String alias) { 310 if (!isValidIdentifier(alias)) 311 throw new IllegalArgumentException("Alias: " + alias + " is not a valid identifier."); 312 synchronized (this) { 313 _nameToUnit.put(alias, unit); 314 } 315 } 316 317 @Override 318 public boolean isValidIdentifier(String name) { 319 if ((name == null) || (name.length() == 0)) 320 return false; 321 /* 322 * for (int i = 0; i < name.length(); i++) { if 323 * (!isUnitIdentifierPart(name.charAt(i))) return false; } 324 */ 325 if (!isUnitIdentifierPart(name.charAt(0))) // label must not begin 326 // with a digit or 327 // mathematical operator 328 return false; 329 return true; 330 } 331 332 static boolean isLetter(char c) { 333 return (c > 64 && c < 91) || (c > 96 && c < 123); 334 } 335 336 // Not necessary but included anyways 337 static boolean isUpperCase(char c) { 338 return c > 64 && c < 91; 339 } 340 341 static boolean isSpace(char c) { 342 // Accounts for spaces and other "space-like" characters 343 return c == 32 || c == 12 || c == 13 || c == 14; 344 } 345 346 static boolean isUnitIdentifierPart(char ch) { 347 return isLetter(ch) 348 || (!Character.isWhitespace(ch) && !Character.isDigit(ch) && (ch != '\u00b7') && (ch != '*') && (ch != '/') && (ch != '(') && (ch != ')') 349 && (ch != '[') && (ch != ']') && (ch != '\u00b9') && (ch != '\u00b2') && (ch != '\u00b3') && (ch != '^') && (ch != '+') && (ch != '-')); 350 } 351 352 // Returns the name for the specified unit or null if product unit. 353 public String nameFor(Unit<?> unit) { 354 // Searches label database. 355 String label = _unitToName.get(unit); 356 if (label != null) 357 return label; 358 if (unit instanceof BaseUnit) 359 return ((BaseUnit<?>) unit).getSymbol(); 360 if (unit instanceof AlternateUnit) 361 return ((AlternateUnit<?>) unit).getSymbol(); 362 if (unit instanceof TransformedUnit) { 363 TransformedUnit<?> tfmUnit = (TransformedUnit<?>) unit; 364 Unit<?> baseUnits = tfmUnit.toSystemUnit(); 365 UnitConverter cvtr = tfmUnit.getSystemConverter(); 366 StringBuffer result = new StringBuffer(); 367 String baseUnitName = baseUnits.toString(); 368 if ((baseUnitName.indexOf('\u00b7') >= 0) || (baseUnitName.indexOf('*') >= 0) || (baseUnitName.indexOf('/') >= 0)) { 369 // We could use parentheses whenever baseUnits is an 370 // instanceof ProductUnit, but most ProductUnits have 371 // aliases, 372 // so we'd end up with a lot of unnecessary parentheses. 373 result.append('('); 374 result.append(baseUnitName); 375 result.append(')'); 376 } else { 377 result.append(baseUnitName); 378 } 379 if (cvtr instanceof AddConverter) { 380 result.append('+'); 381 result.append(((AddConverter) cvtr).getOffset()); 382 } else if (cvtr instanceof RationalConverter) { 383 double dividend = ((RationalConverter) cvtr).getDividend(); 384 if (dividend != 1) { 385 result.append('*'); 386 result.append(dividend); 387 } 388 double divisor = ((RationalConverter) cvtr).getDivisor(); 389 if (divisor != 1) { 390 result.append('/'); 391 result.append(divisor); 392 } 393 ; 394 } else if (cvtr instanceof MultiplyConverter) { 395 result.append('*'); 396 result.append(((MultiplyConverter) cvtr).getFactor()); 397 } else { // Other converters. 398 return "[" + baseUnits + "?]"; 399 } 400 return result.toString(); 401 } 402 // Compound unit. 403 // if (unit instanceof CompoundUnit) { 404 // CompoundUnit<?> cpdUnit = (CompoundUnit<?>) unit; 405 // return nameFor(cpdUnit.getHigher()).toString() + ":" 406 // + nameFor(cpdUnit.getLower()); 407 // } 408 return null; // Product unit. 409 } 410 411 // Returns the unit for the specified name. 412 public Unit<?> unitFor(String name) { 413 Unit<?> unit = _nameToUnit.get(name); 414 if (unit != null) 415 return unit; 416 unit = SYMBOL_TO_UNIT.get(name); 417 return unit; 418 } 419 420 // ////////////////////////// 421 // Parsing. 422 423 @SuppressWarnings("rawtypes") 424 public Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) throws ParserException { 425 int startIndex = pos.getIndex(); 426 String name = readIdentifier(csq, pos); 427 Unit<?> unit = unitFor(name); 428 check(unit != null, name + " not recognized", csq, startIndex); 429 return unit; 430 } 431 432 @SuppressWarnings("rawtypes") 433 @Override 434 public Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) throws ParserException { 435 Unit<?> result = AbstractUnit.ONE; 436 int token = nextToken(csq, pos); 437 switch (token) { 438 case IDENTIFIER: 439 result = parseSingleUnit(csq, pos); 440 break; 441 case OPEN_PAREN: 442 pos.setIndex(pos.getIndex() + 1); 443 result = parseProductUnit(csq, pos); 444 token = nextToken(csq, pos); 445 check(token == CLOSE_PAREN, "')' expected", csq, pos.getIndex()); 446 pos.setIndex(pos.getIndex() + 1); 447 break; 448 } 449 token = nextToken(csq, pos); 450 while (true) { 451 switch (token) { 452 case EXPONENT: 453 Exponent e = readExponent(csq, pos); 454 if (e.pow != 1) { 455 result = result.pow(e.pow); 456 } 457 if (e.root != 1) { 458 result = result.root(e.root); 459 } 460 break; 461 case MULTIPLY: 462 pos.setIndex(pos.getIndex() + 1); 463 token = nextToken(csq, pos); 464 if (token == INTEGER) { 465 long n = readLong(csq, pos); 466 if (n != 1) { 467 result = result.multiply(n); 468 } 469 } else if (token == FLOAT) { 470 double d = readDouble(csq, pos); 471 if (d != 1.0) { 472 result = result.multiply(d); 473 } 474 } else { 475 result = result.multiply(parseProductUnit(csq, pos)); 476 } 477 break; 478 case DIVIDE: 479 pos.setIndex(pos.getIndex() + 1); 480 token = nextToken(csq, pos); 481 if (token == INTEGER) { 482 long n = readLong(csq, pos); 483 if (n != 1) { 484 result = result.divide(n); 485 } 486 } else if (token == FLOAT) { 487 double d = readDouble(csq, pos); 488 if (d != 1.0) { 489 result = result.divide(d); 490 } 491 } else { 492 result = result.divide(parseProductUnit(csq, pos)); 493 } 494 break; 495 case PLUS: 496 pos.setIndex(pos.getIndex() + 1); 497 token = nextToken(csq, pos); 498 if (token == INTEGER) { 499 long n = readLong(csq, pos); 500 if (n != 1) { 501 result = result.shift(n); 502 } 503 } else if (token == FLOAT) { 504 double d = readDouble(csq, pos); 505 if (d != 1.0) { 506 result = result.shift(d); 507 } 508 } else { 509 throw new ParserException("not a number", pos.getIndex()); 510 } 511 break; 512 case EOF: 513 case CLOSE_PAREN: 514 return result; 515 default: 516 throw new ParserException("unexpected token " + token, pos.getIndex()); 517 } 518 token = nextToken(csq, pos); 519 } 520 } 521 522 private static final int EOF = 0; 523 private static final int IDENTIFIER = 1; 524 private static final int OPEN_PAREN = 2; 525 private static final int CLOSE_PAREN = 3; 526 private static final int EXPONENT = 4; 527 private static final int MULTIPLY = 5; 528 private static final int DIVIDE = 6; 529 private static final int PLUS = 7; 530 private static final int INTEGER = 8; 531 private static final int FLOAT = 9; 532 533 private int nextToken(CharSequence csq, ParsePosition pos) { 534 final int length = csq.length(); 535 while (pos.getIndex() < length) { 536 char c = csq.charAt(pos.getIndex()); 537 if (isUnitIdentifierPart(c)) { 538 return IDENTIFIER; 539 } else if (c == '(') { 540 return OPEN_PAREN; 541 } else if (c == ')') { 542 return CLOSE_PAREN; 543 } else if ((c == '^') || (c == '\u00b9') || (c == '\u00b2') || (c == '\u00b3')) { 544 return EXPONENT; 545 } else if (c == '*') { 546 char c2 = csq.charAt(pos.getIndex() + 1); 547 if (c2 == '*') { 548 return EXPONENT; 549 } else { 550 return MULTIPLY; 551 } 552 } else if (c == '\u00b7') { 553 return MULTIPLY; 554 } else if (c == '/') { 555 return DIVIDE; 556 } else if (c == '+') { 557 return PLUS; 558 } else if ((c == '-') || Character.isDigit(c)) { 559 int index = pos.getIndex() + 1; 560 while ((index < length) && (Character.isDigit(c) || (c == '-') || (c == '.') || (c == 'E'))) { 561 c = csq.charAt(index++); 562 if (c == '.') { 563 return FLOAT; 564 } 565 } 566 return INTEGER; 567 } 568 pos.setIndex(pos.getIndex() + 1); 569 } 570 return EOF; 571 } 572 573 private void check(boolean expr, String message, CharSequence csq, int index) throws ParserException { 574 if (!expr) { 575 throw new ParserException(message + " (in " + csq + " at index " + index + ")", index); 576 } 577 } 578 579 private Exponent readExponent(CharSequence csq, ParsePosition pos) { 580 char c = csq.charAt(pos.getIndex()); 581 if (c == '^') { 582 pos.setIndex(pos.getIndex() + 1); 583 } else if (c == '*') { 584 pos.setIndex(pos.getIndex() + 2); 585 } 586 final int length = csq.length(); 587 int pow = 0; 588 boolean isPowNegative = false; 589 int root = 0; 590 boolean isRootNegative = false; 591 boolean isRoot = false; 592 while (pos.getIndex() < length) { 593 c = csq.charAt(pos.getIndex()); 594 if (c == '\u00b9') { 595 if (isRoot) { 596 root = root * 10 + 1; 597 } else { 598 pow = pow * 10 + 1; 599 } 600 } else if (c == '\u00b2') { 601 if (isRoot) { 602 root = root * 10 + 2; 603 } else { 604 pow = pow * 10 + 2; 605 } 606 } else if (c == '\u00b3') { 607 if (isRoot) { 608 root = root * 10 + 3; 609 } else { 610 pow = pow * 10 + 3; 611 } 612 } else if (c == '-') { 613 if (isRoot) { 614 isRootNegative = true; 615 } else { 616 isPowNegative = true; 617 } 618 } else if ((c >= '0') && (c <= '9')) { 619 if (isRoot) { 620 root = root * 10 + (c - '0'); 621 } else { 622 pow = pow * 10 + (c - '0'); 623 } 624 } else if (c == ':') { 625 isRoot = true; 626 } else { 627 break; 628 } 629 pos.setIndex(pos.getIndex() + 1); 630 } 631 if (pow == 0) 632 pow = 1; 633 if (root == 0) 634 root = 1; 635 return new Exponent(isPowNegative ? -pow : pow, isRootNegative ? -root : root); 636 } 637 638 private long readLong(CharSequence csq, ParsePosition pos) { 639 final int length = csq.length(); 640 int result = 0; 641 boolean isNegative = false; 642 while (pos.getIndex() < length) { 643 char c = csq.charAt(pos.getIndex()); 644 if (c == '-') { 645 isNegative = true; 646 } else if ((c >= '0') && (c <= '9')) { 647 result = result * 10 + (c - '0'); 648 } else { 649 break; 650 } 651 pos.setIndex(pos.getIndex() + 1); 652 } 653 return isNegative ? -result : result; 654 } 655 656 // TODO change ParsePos to int arguments 657 private double readDouble(CharSequence csq, ParsePosition pos) { 658 final int length = csq.length(); 659 int start = pos.getIndex(); 660 int end = start + 1; 661 while (end < length) { 662 if ("0123456789+-.E".indexOf(csq.charAt(end)) < 0) { 663 break; 664 } 665 end += 1; 666 } 667 pos.setIndex(end + 1); 668 return Double.parseDouble(csq.subSequence(start, end).toString()); 669 } 670 671 private String readIdentifier(CharSequence csq, ParsePosition pos) { 672 final int length = csq.length(); 673 int start = pos.getIndex(); 674 int i = start; 675 while ((++i < length) && isUnitIdentifierPart(csq.charAt(i))) { 676 } 677 pos.setIndex(i); 678 return csq.subSequence(start, i).toString(); 679 } 680 681 // ////////////////////////// 682 // Formatting. 683 684 @Override 685 public Appendable format(Unit<?> unit, Appendable appendable) throws IOException { 686 String name = nameFor(unit); 687 if (name != null) 688 return appendable.append(name); 689 if (!(unit instanceof ProductUnit)) 690 throw new IllegalArgumentException("Cannot format given Object as a Unit"); 691 692 // Product unit. 693 ProductUnit<?> productUnit = (ProductUnit<?>) unit; 694 int invNbr = 0; 695 696 // Write positive exponents first. 697 boolean start = true; 698 for (int i = 0; i < productUnit.getUnitCount(); i++) { 699 int pow = productUnit.getUnitPow(i); 700 if (pow >= 0) { 701 if (!start) { 702 appendable.append('\u00b7'); // Separator. 703 } 704 name = nameFor(productUnit.getUnit(i)); 705 int root = productUnit.getUnitRoot(i); 706 append(appendable, name, pow, root); 707 start = false; 708 } else { 709 invNbr++; 710 } 711 } 712 713 // Write negative exponents. 714 if (invNbr != 0) { 715 if (start) { 716 appendable.append('1'); // e.g. 1/s 717 } 718 appendable.append('/'); 719 if (invNbr > 1) { 720 appendable.append('('); 721 } 722 start = true; 723 for (int i = 0; i < productUnit.getUnitCount(); i++) { 724 int pow = productUnit.getUnitPow(i); 725 if (pow < 0) { 726 name = nameFor(productUnit.getUnit(i)); 727 int root = productUnit.getUnitRoot(i); 728 if (!start) { 729 appendable.append('\u00b7'); // Separator. 730 } 731 append(appendable, name, -pow, root); 732 start = false; 733 } 734 } 735 if (invNbr > 1) { 736 appendable.append(')'); 737 } 738 } 739 return appendable; 740 } 741 742 private void append(Appendable appendable, CharSequence symbol, int pow, int root) throws IOException { 743 appendable.append(symbol); 744 if ((pow != 1) || (root != 1)) { 745 // Write exponent. 746 if ((pow == 2) && (root == 1)) { 747 appendable.append('\u00b2'); // Square 748 } else if ((pow == 3) && (root == 1)) { 749 appendable.append('\u00b3'); // Cubic 750 } else { 751 // Use general exponent form. 752 appendable.append('^'); 753 appendable.append(String.valueOf(pow)); 754 if (root != 1) { 755 appendable.append(':'); 756 appendable.append(String.valueOf(root)); 757 } 758 } 759 } 760 } 761 762 // private static final long serialVersionUID = 1L; 763 764 public Unit<?> parse(CharSequence csq) throws ParserException { 765 return parse(csq, 0); 766 } 767 768 @Override 769 protected Unit<?> parse(CharSequence csq, int index) throws ParserException { 770 return parseObject(csq.toString(), new ParsePosition(index)); 771 } 772 } 773 774 /** 775 * This class represents the ASCII format. 776 */ 777 protected static final class ASCIIFormat extends DefaultFormat { 778 779 @Override 780 public String nameFor(Unit<?> unit) { 781 // First search if specific ASCII name should be used. 782 String name = _unitToName.get(unit); 783 if (name != null) 784 return name; 785 // Else returns default name. 786 return DEFAULT.nameFor(unit); 787 } 788 789 @Override 790 public Unit<?> unitFor(String name) { 791 // First search if specific ASCII name. 792 Unit<?> unit = _nameToUnit.get(name); 793 if (unit != null) 794 return unit; 795 // Else returns default mapping. 796 return DEFAULT.unitFor(name); 797 } 798 799 @Override 800 public Appendable format(Unit<?> unit, Appendable appendable) throws IOException { 801 String name = nameFor(unit); 802 if (name != null) 803 return appendable.append(name); 804 if (!(unit instanceof ProductUnit)) 805 throw new IllegalArgumentException("Cannot format given Object as a Unit"); 806 807 ProductUnit<?> productUnit = (ProductUnit<?>) unit; 808 for (int i = 0; i < productUnit.getUnitCount(); i++) { 809 if (i != 0) { 810 appendable.append('*'); // Separator. 811 } 812 name = nameFor(productUnit.getUnit(i)); 813 int pow = productUnit.getUnitPow(i); 814 int root = productUnit.getUnitRoot(i); 815 appendable.append(name); 816 if ((pow != 1) || (root != 1)) { 817 // Use general exponent form. 818 appendable.append('^'); 819 appendable.append(String.valueOf(pow)); 820 if (root != 1) { 821 appendable.append(':'); 822 appendable.append(String.valueOf(root)); 823 } 824 } 825 } 826 return appendable; 827 } 828 829 @Override 830 public boolean isValidIdentifier(String name) { 831 if ((name == null) || (name.length() == 0)) 832 return false; 833 if (!isUnitIdentifierPart(name.charAt(0))) // label must not begin 834 // with a digit or 835 // mathematical operator 836 return false; 837 return isAllASCII(name); 838 /* 839 * for (int i = 0; i < name.length(); i++) { if 840 * (!isAsciiCharacter(name.charAt(i))) return false; } return true; 841 */ 842 } 843 844 // private static final long serialVersionUID = 1L; 845 } 846 847 /* 848 * // return if the given character is within the ASCII charset private 849 * static boolean isAsciiCharacter(char c) { // return (c > 64 && c < 91) || 850 * (c > 96 && c < 123); return (int) c < 128; } 851 */ 852 // to check if a string only contains US-ASCII characters 853 // 854 protected static boolean isAllASCII(String input) { 855 boolean isASCII = true; 856 for (int i = 0; i < input.length(); i++) { 857 int c = input.charAt(i); 858 if (c > 0x7F) { 859 isASCII = false; 860 break; 861 } 862 } 863 return isASCII; 864 } 865 866 /** 867 * Holds the unique symbols collection (base units or alternate units). 868 */ 869 private static final Map<String, Unit<?>> SYMBOL_TO_UNIT = new HashMap<String, Unit<?>>(); 870 871 // ////////////////////////////////////////////////////////////////////////// 872 // Initializes the standard unit database for SI units. 873 874 private static final Unit<?>[] SI_UNITS = { Units.AMPERE, Units.BECQUEREL, Units.CANDELA, Units.COULOMB, Units.FARAD, Units.GRAY, Units.HENRY, 875 Units.HERTZ, Units.JOULE, Units.KATAL, Units.KELVIN, Units.LUMEN, Units.LUX, Units.METRE, Units.MOLE, Units.NEWTON, Units.OHM, Units.PASCAL, 876 Units.RADIAN, Units.SECOND, Units.SIEMENS, Units.SIEVERT, Units.STERADIAN, Units.TESLA, Units.VOLT, Units.WATT, Units.WEBER }; 877 878 private static final String[] PREFIXES = { YOTTA.getSymbol(), ZETTA.getSymbol(), EXA.getSymbol(), PETA.getSymbol(), TERA.getSymbol(), 879 GIGA.getSymbol(), MEGA.getSymbol(), KILO.getSymbol(), HECTO.getSymbol(), DEKA.getSymbol(), DECI.getSymbol(), CENTI.getSymbol(), 880 MILLI.getSymbol(), MICRO.getSymbol(), NANO.getSymbol(), PICO.getSymbol(), FEMTO.getSymbol(), ATTO.getSymbol(), ZEPTO.getSymbol(), 881 YOCTO.getSymbol() }; 882 883 // XXX we could try retrieving this dynamically in a static {} method from 884 // MetricPrefix if symbols above are also aligned 885 private static final UnitConverter[] CONVERTERS = { YOTTA.getConverter(), ZETTA.getConverter(), EXA.getConverter(), PETA.getConverter(), 886 TERA.getConverter(), GIGA.getConverter(), MEGA.getConverter(), KILO.getConverter(), HECTO.getConverter(), DEKA.getConverter(), 887 DECI.getConverter(), CENTI.getConverter(), MILLI.getConverter(), MICRO.getConverter(), NANO.getConverter(), PICO.getConverter(), 888 FEMTO.getConverter(), ATTO.getConverter(), ZEPTO.getConverter(), YOCTO.getConverter() }; 889 890 private static String asciiPrefix(String prefix) { 891 return prefix == "µ" ? "micro" : prefix; 892 } 893 894 static { 895 for (int i = 0; i < SI_UNITS.length; i++) { 896 Unit<?> si = SI_UNITS[i]; 897 String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si).getSymbol() : ((AlternateUnit<?>) si).getSymbol(); 898 DEFAULT.label(si, symbol); 899 if (isAllASCII(symbol)) 900 ASCII.label(si, symbol); 901 for (int j = 0; j < PREFIXES.length; j++) { 902 Unit<?> u = si.transform(CONVERTERS[j]); 903 DEFAULT.label(u, PREFIXES[j] + symbol); 904 if (PREFIXES[j] == "µ") { 905 ASCII.label(u, "micro"); // + symbol); 906 } 907 } 908 } 909 // Special case for KILOGRAM. 910 DEFAULT.label(Units.GRAM, "g"); 911 for (int i = 0; i < PREFIXES.length; i++) { 912 if (CONVERTERS[i] == KILO.getConverter()) // TODO should it better 913 // be equals()? 914 continue; // kg is already defined. 915 DEFAULT.label(Units.KILOGRAM.transform(CONVERTERS[i].concatenate(MILLI.getConverter())), PREFIXES[i] + "g"); 916 if (PREFIXES[i] == "µ") { 917 ASCII.label(Units.KILOGRAM.transform(CONVERTERS[i].concatenate(MILLI.getConverter())), "microg"); 918 } 919 } 920 921 // Alias and ASCIIFormat for Ohm 922 DEFAULT.alias(Units.OHM, "Ohm"); 923 ASCII.label(Units.OHM, "Ohm"); 924 for (int i = 0; i < PREFIXES.length; i++) { 925 DEFAULT.alias(Units.OHM.transform(CONVERTERS[i]), PREFIXES[i] + "Ohm"); 926 ASCII.label(Units.OHM.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Ohm"); 927 } 928 929 // Special case for DEGREE_CElSIUS. 930 // DEFAULT.label(Units.CELSIUS, "°C"); 931 DEFAULT.label(Units.CELSIUS, "\u00b0C"); 932 DEFAULT.alias(Units.CELSIUS, "?"); 933 // DEFAULT.alias(Units.CELSIUS, "°C"); 934 ASCII.label(Units.CELSIUS, "Celsius"); 935 for (int i = 0; i < PREFIXES.length; i++) { 936 DEFAULT.label(Units.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "?"); 937 DEFAULT.alias(Units.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "°C"); 938 ASCII.label(Units.CELSIUS.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Celsius"); 939 } 940 941 DEFAULT.label(Units.PERCENT, "%"); 942 DEFAULT.label(Units.KILOGRAM, "kg"); 943 DEFAULT.label(Units.METRE, "m"); 944 DEFAULT.label(Units.SECOND, "s"); 945 DEFAULT.label(Units.MINUTE, "min"); 946 DEFAULT.label(Units.HOUR, "h"); 947 DEFAULT.label(Units.DAY, "day"); 948 DEFAULT.label(Units.WEEK, "week"); 949 DEFAULT.label(Units.YEAR, "year"); 950 DEFAULT.label(Units.KILOMETRES_PER_HOUR, "km/h"); 951 DEFAULT.label(Units.CUBIC_METRE, "\u33A5"); 952 ASCII.label(Units.CUBIC_METRE, "m3"); 953 ASCII.label(LITRE, "l"); 954 DEFAULT.label(LITRE, "l"); 955 DEFAULT.label(MetricPrefix.NANO(LITRE), "nl"); 956 ASCII.label(MetricPrefix.NANO(LITRE), "nl"); 957 DEFAULT.label(MetricPrefix.MICRO(LITRE), "µl"); 958 ASCII.label(MetricPrefix.MICRO(LITRE), "microL"); 959 ASCII.label(MetricPrefix.MILLI(LITRE), "mL"); 960 DEFAULT.label(MetricPrefix.MILLI(LITRE), "ml"); 961 ASCII.label(MetricPrefix.CENTI(LITRE), "cL"); 962 DEFAULT.label(MetricPrefix.CENTI(LITRE), "cl"); 963 ASCII.label(MetricPrefix.DECI(LITRE), "dL"); 964 DEFAULT.label(Units.NEWTON, "N"); 965 ASCII.label(Units.NEWTON, "N"); 966 } 967}