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