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