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