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