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