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