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.8, Apr. 20, 2021 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 private static final String MU = "\u03bc"; 104 105 /** 106 * Holds the standard unit format. 107 */ 108 private static final DefaultFormat DEFAULT = new DefaultFormat().init(); 109 110 /** 111 * Holds the ASCIIFormat flavor. 112 */ 113 private static final ASCIIFormat ASCII = new ASCIIFormat().init(); 114 115 116 /** 117 * Returns the globally shared unit format instance (used by {@link AbstractUnit#parse(CharSequence) AbstractUnit.parse()} and 118 * {@link AbstractUnit#toString() AbstractUnit.toString()}). 119 * 120 * @return the default unit format. 121 */ 122 public static SimpleUnitFormat getInstance() { 123 return getInstance(Flavor.Default); 124 } 125 126 /** 127 * Returns the {@link SimpleUnitFormat} in the desired {@link Flavor} 128 * 129 * @return the instance for the given {@link Flavor}. 130 */ 131 public static SimpleUnitFormat getInstance(Flavor flavor) { 132 switch (flavor) { 133 case ASCII: 134 return SimpleUnitFormat.ASCII; 135 default: 136 return DEFAULT; 137 } 138 } 139 140 /** 141 * Similar to {@link #getInstance()}, but returns a new, non-shared unit format instance, 142 * instead of a shared singleton instance. 143 * 144 * @return a new instance of the default unit format. 145 * @see #getInstance() 146 */ 147 public static SimpleUnitFormat getNewInstance() { 148 return getNewInstance(Flavor.Default); 149 } 150 151 /** 152 * Similar to {@link #getInstance(Flavor)}, but returns a new {@link SimpleUnitFormat} instance in the desired 153 * {@link Flavor}, instead of a shared singleton instance. 154 * 155 * @return a new instance for the given {@link Flavor}. 156 * @see #getInstance(Flavor) 157 */ 158 public static SimpleUnitFormat getNewInstance(Flavor flavor) { 159 switch (flavor) { 160 case ASCII: 161 return new ASCIIFormat().init(); 162 default: 163 return new DefaultFormat().init(); 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 MeasurementError(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 default (Unicode) format. 318 * @deprecated internal class, that will be made private soon, please extend either SimpleUnitFormat or AbstractUnitFormat 319 */ 320 protected static class DefaultFormat extends SimpleUnitFormat { 321 322 // Initializes the standard unit databases. 323 324 static final Unit<?>[] METRIC_UNITS = { Units.AMPERE, Units.BECQUEREL, Units.CANDELA, Units.COULOMB, Units.FARAD, Units.GRAY, Units.HENRY, 325 Units.HERTZ, Units.JOULE, Units.KATAL, Units.KELVIN, Units.LUMEN, Units.LUX, Units.METRE, Units.MOLE, Units.NEWTON, Units.OHM, Units.PASCAL, 326 Units.RADIAN, Units.SECOND, Units.SIEMENS, Units.SIEVERT, Units.STERADIAN, Units.TESLA, Units.VOLT, Units.WATT, Units.WEBER }; 327 328 static final String[] METRIC_PREFIX_SYMBOLS = 329 Stream.of(MetricPrefix.values()) 330 .map(Prefix::getSymbol) 331 .collect(Collectors.toList()) 332 .toArray(new String[] {}); 333 334 // TODO try to consolidate those 335 static final UnitConverter[] METRIC_PREFIX_CONVERTERS = 336 Stream.of(MetricPrefix.values()) 337 .map(MultiplyConverter::ofPrefix) 338 .collect(Collectors.toList()) 339 .toArray(new UnitConverter[] {}); 340 341 static final String[] BINARY_PREFIX_SYMBOLS = 342 Stream.of(BinaryPrefix.values()) 343 .map(Prefix::getSymbol) 344 .collect(Collectors.toList()) 345 .toArray(new String[] {}); 346 347 static final UnitConverter[] BINARY_PREFIX_CONVERTERS = 348 Stream.of(BinaryPrefix.values()) 349 .map(MultiplyConverter::ofPrefix) 350 .collect(Collectors.toList()) 351 .toArray(new UnitConverter[] {}); 352 353 /** 354 * Holds the unique symbols collection (base units or alternate units). 355 */ 356 private final Map<String, Unit<?>> symbolToUnit = new HashMap<>(); 357 358 private static enum Token { EOF, IDENTIFIER, OPEN_PAREN, CLOSE_PAREN, EXPONENT, MULTIPLY, DIVIDE, 359 PLUS, INTEGER, FLOAT }; 360 361 362 DefaultFormat() { 363 364 // Hack, somehow µg is not found. 365 symbolToUnit.put(MetricPrefix.MICRO.getSymbol() + "g", MICRO(Units.GRAM)); 366 symbolToUnit.put("μg", MICRO(Units.GRAM)); 367 symbolToUnit.put(MU + "g", MICRO(Units.GRAM)); 368 } 369 370 private DefaultFormat init() { 371 372 for (int i = 0; i < METRIC_UNITS.length; i++) { 373 Unit<?> si = METRIC_UNITS[i]; 374 String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si).getSymbol() : ((AlternateUnit<?>) si).getSymbol(); 375 label(si, symbol); 376 for (int j = 0; j < METRIC_PREFIX_SYMBOLS.length; j++) { 377 Unit<?> u = si.prefix(MetricPrefix.values()[j]); 378 label(u, METRIC_PREFIX_SYMBOLS[j] + symbol); 379 if ( "µ".equals(METRIC_PREFIX_SYMBOLS[j]) ) { 380 label(u, MU + symbol); 381 } 382 } // TODO what about BINARY_PREFIX here? 383 } 384 385 // -- GRAM/KILOGRAM 386 387 label(Units.GRAM, "g"); 388 for(MetricPrefix prefix : MetricPrefix.values()) { 389 switch (prefix) { 390 case KILO: 391 label(Units.KILOGRAM, "kg"); 392 break; 393 case MICRO: 394 label(Units.GRAM.prefix(prefix), prefix.getSymbol()+"g"); 395 break; 396 default: 397 label(Units.GRAM.prefix(prefix), prefix.getSymbol()+"g"); 398 break; 399 } 400 } 401 402 label(MICRO(Units.GRAM), MetricPrefix.MICRO.getSymbol() + "g"); 403 404 // Alias and ASCIIFormat for Ohm 405 alias(Units.OHM, "Ohm"); 406 for (int i = 0; i < METRIC_PREFIX_SYMBOLS.length; i++) { 407 alias(Units.OHM.prefix(MetricPrefix.values()[i]), METRIC_PREFIX_SYMBOLS[i] + "Ohm"); 408 } 409 410 // Special case for DEGREE_CELSIUS. 411 label(Units.CELSIUS, "℃"); 412 alias(Units.CELSIUS, "°C"); 413 for (int i = 0; i < METRIC_PREFIX_SYMBOLS.length; i++) { 414 label(Units.CELSIUS.prefix(MetricPrefix.values()[i]), METRIC_PREFIX_SYMBOLS[i] + "℃"); 415 alias(Units.CELSIUS.prefix(MetricPrefix.values()[i]), METRIC_PREFIX_SYMBOLS[i] + "°C"); 416 } 417 418 label(Units.PERCENT, "%"); 419 label(Units.METRE, "m"); 420 label(Units.SECOND, "s"); 421 label(Units.MINUTE, "min"); 422 label(Units.HOUR, "h"); 423 label(Units.DAY, "day"); 424 alias(Units.DAY, "d"); 425 label(Units.WEEK, "week"); 426 label(Units.YEAR, "year"); 427 alias(Units.YEAR, "days365"); 428 label(Units.MONTH, "mo"); 429 alias(Units.MONTH, "mon"); 430 alias(Units.MONTH, "month"); 431 label(Units.KILOMETRE_PER_HOUR, "km/h"); 432 label(Units.CUBIC_METRE, "\u33A5"); 433 434 // -- LITRE 435 436 label(Units.LITRE, "l"); 437 for(Prefix prefix : MetricPrefix.values()) { 438 label(Units.LITRE.prefix(prefix), prefix.getSymbol()+"l"); 439 } 440 label(Units.NEWTON, "N"); 441 label(Units.RADIAN, "rad"); 442 443 label(AbstractUnit.ONE, "one"); 444 445 alias(Units.SQUARE_METRE, "m2"); 446 alias(Units.CUBIC_METRE, "m3"); 447 448 return this; 449 } 450 451 452 /** 453 * Holds the name to unit mapping. 454 */ 455 protected final Map<String, Unit<?>> nameToUnit = new HashMap<>(); 456 457 /** 458 * Holds the unit to name mapping. 459 */ 460 protected final Map<Unit<?>, String> unitToName = new HashMap<>(); 461 462 @Override 463 public String toString() { 464 return "SimpleUnitFormat"; 465 } 466 467 @Override 468 public void label(Unit<?> unit, String label) { 469 if (!isValidIdentifier(label)) 470 throw new IllegalArgumentException("Label: " + label + " is not a valid identifier."); 471 synchronized (this) { 472 nameToUnit.put(label, unit); 473 unitToName.put(unit, label); 474 } 475 } 476 477 @Override 478 public void alias(Unit<?> unit, String alias) { 479 if (!isValidIdentifier(alias)) 480 throw new IllegalArgumentException("Alias: " + alias + " is not a valid identifier."); 481 synchronized (this) { 482 nameToUnit.put(alias, unit); 483 } 484 } 485 486 @Override 487 protected boolean isValidIdentifier(String name) { 488 if ((name == null) || (name.length() == 0)) 489 return false; 490 return isUnitIdentifierPart(name.charAt(0)); 491 } 492 493 protected static boolean isUnitIdentifierPart(char ch) { 494 return Character.isLetter(ch) 495 || (!Character.isWhitespace(ch) && !Character.isDigit(ch) && (ch != MIDDLE_DOT) && (ch != '*') && (ch != '/') && (ch != '(') && (ch != ')') 496 && (ch != '[') && (ch != ']') && (ch != '\u00b9') && (ch != '\u00b2') && (ch != '\u00b3') && (ch != '^') && (ch != '+') && (ch != '-')); 497 } 498 499 // Returns the name for the specified unit or null if product unit. 500 protected String nameFor(Unit<?> unit) { 501 // Searches label database. 502 String label = unitToName.get(unit); 503 if (label != null) 504 return label; 505 if (unit instanceof BaseUnit) 506 return ((BaseUnit<?>) unit).getSymbol(); 507 if (unit instanceof AlternateUnit) 508 return ((AlternateUnit<?>) unit).getSymbol(); 509 if (unit instanceof TransformedUnit) { 510 TransformedUnit<?> tfmUnit = (TransformedUnit<?>) unit; 511 if (tfmUnit.getSymbol() != null) { 512 return tfmUnit.getSymbol(); 513 } 514 Unit<?> baseUnit = tfmUnit.getParentUnit(); 515 UnitConverter cvtr = tfmUnit.getConverter(); // tfmUnit.getSystemConverter(); 516 StringBuilder result = new StringBuilder(); 517 String baseUnitName = baseUnit.toString(); 518 String prefix = prefixFor(cvtr); 519 if ((baseUnitName.indexOf(MIDDLE_DOT) >= 0) || (baseUnitName.indexOf('*') >= 0) || (baseUnitName.indexOf('/') >= 0)) { 520 // We could use parentheses whenever baseUnits is an 521 // instanceof ProductUnit, but most ProductUnits have 522 // aliases, 523 // so we'd end up with a lot of unnecessary parentheses. 524 result.append('('); 525 result.append(baseUnitName); 526 result.append(')'); 527 } else { 528 result.append(baseUnitName); 529 } 530 if (prefix != null) { 531 result.insert(0, prefix); 532 } else { 533 if (cvtr instanceof AddConverter) { 534 result.append('+'); 535 result.append(((AddConverter) cvtr).getOffset()); 536 } else if (cvtr instanceof MultiplyConverter) { 537 Number scaleFactor = ((MultiplyConverter) cvtr).getFactor(); 538 if(scaleFactor instanceof RationalNumber) { 539 540 RationalNumber rational = (RationalNumber)scaleFactor; 541 RationalNumber reciprocal = rational.reciprocal(); 542 if(reciprocal.isInteger()) { 543 result.append('/'); 544 result.append(reciprocal.toString()); // renders as integer 545 } else { 546 result.append('*'); 547 result.append(scaleFactor); 548 } 549 550 } else { 551 result.append('*'); 552 result.append(scaleFactor); 553 } 554 555 } else { // Other converters. 556 return "[" + baseUnit + "?]"; 557 } 558 } 559 return result.toString(); 560 } 561 if (unit instanceof AnnotatedUnit<?>) { 562 AnnotatedUnit<?> annotatedUnit = (AnnotatedUnit<?>) unit; 563 final StringBuilder annotable = new StringBuilder(nameFor(annotatedUnit.getActualUnit())); 564 if (annotatedUnit.getAnnotation() != null) { 565 annotable.append('{'); // TODO maybe also configure this one similar to mix delimiter 566 annotable.append(annotatedUnit.getAnnotation()); 567 annotable.append('}'); 568 } 569 return annotable.toString(); 570 } 571 return null; // Product unit. 572 } 573 574 // Returns the prefix for the specified unit converter. 575 protected String prefixFor(UnitConverter converter) { 576 for (int i = 0; i < METRIC_PREFIX_CONVERTERS.length; i++) { 577 if (METRIC_PREFIX_CONVERTERS[i].equals(converter)) { 578 return METRIC_PREFIX_SYMBOLS[i]; 579 } 580 } 581 for (int j = 0; j < BINARY_PREFIX_CONVERTERS.length; j++) { 582 if (BINARY_PREFIX_CONVERTERS[j].equals(converter)) { 583 return BINARY_PREFIX_SYMBOLS[j]; 584 } 585 } 586 return null; // TODO or return blank? 587 } 588 589 // Returns the unit for the specified name. 590 protected Unit<?> unitFor(String name) { 591 Unit<?> unit = nameToUnit.get(name); 592 if (unit != null) { 593 return unit; 594 } else { 595 unit = symbolToUnit.get(name); 596 } 597 return unit; 598 } 599 600 // ////////////////////////// 601 // Parsing. 602 @SuppressWarnings({ "rawtypes", "unchecked" }) 603 public Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) throws MeasurementParseException { 604 int startIndex = pos.getIndex(); 605 String name = readIdentifier(csq, pos); 606 Unit unit = unitFor(name); 607 check(unit != null, name + " not recognized", csq, startIndex); 608 return unit; 609 } 610 611 @SuppressWarnings({ "rawtypes", "unchecked" }) 612 @Override 613 public Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) throws MeasurementParseException { 614 Unit result = null; 615 if (csq == null) { 616 throw new MeasurementParseException("Cannot parse null", csq, pos.getIndex()); 617 } else { 618 result = unitFor(csq.toString()); 619 if (result != null) 620 return result; 621 } 622 result = AbstractUnit.ONE; 623 Token token = nextToken(csq, pos); 624 switch (token) { 625 case IDENTIFIER: 626 result = parseSingleUnit(csq, pos); 627 break; 628 case OPEN_PAREN: 629 pos.setIndex(pos.getIndex() + 1); 630 result = parseProductUnit(csq, pos); 631 token = nextToken(csq, pos); 632 check(token == Token.CLOSE_PAREN, "')' expected", csq, pos.getIndex()); 633 pos.setIndex(pos.getIndex() + 1); 634 break; 635 default: 636 break; 637 } 638 token = nextToken(csq, pos); 639 while (true) { 640 switch (token) { 641 case EXPONENT: 642 Exponent e = readExponent(csq, pos); 643 if (e.pow != 1) { 644 result = result.pow(e.pow); 645 } 646 if (e.root != 1) { 647 result = result.root(e.root); 648 } 649 break; 650 case MULTIPLY: 651 pos.setIndex(pos.getIndex() + 1); 652 token = nextToken(csq, pos); 653 if (token == Token.INTEGER) { 654 long n = readLong(csq, pos); 655 if (n != 1) { 656 result = result.multiply(n); 657 } 658 } else if (token == Token.FLOAT) { 659 double d = readDouble(csq, pos); 660 if (d != 1.0) { 661 result = result.multiply(d); 662 } 663 } else { 664 result = result.multiply(parseProductUnit(csq, pos)); 665 } 666 break; 667 case DIVIDE: 668 pos.setIndex(pos.getIndex() + 1); 669 token = nextToken(csq, pos); 670 if (token == Token.INTEGER) { 671 long n = readLong(csq, pos); 672 if (n != 1) { 673 result = result.divide(n); 674 } 675 } else if (token == Token.FLOAT) { 676 double d = readDouble(csq, pos); 677 if (d != 1.0) { 678 result = result.divide(d); 679 } 680 } else { 681 result = result.divide(parseProductUnit(csq, pos)); 682 } 683 break; 684 case PLUS: 685 pos.setIndex(pos.getIndex() + 1); 686 token = nextToken(csq, pos); 687 if (token == Token.INTEGER) { 688 long n = readLong(csq, pos); 689 if (n != 1) { 690 result = result.shift(n); 691 } 692 } else if (token == Token.FLOAT) { 693 double d = readDouble(csq, pos); 694 if (d != 1.0) { 695 result = result.shift(d); 696 } 697 } else { 698 throw new MeasurementParseException("not a number", csq, pos.getIndex()); 699 } 700 break; 701 case EOF: 702 case CLOSE_PAREN: 703 return result; 704 default: 705 throw new MeasurementParseException("unexpected token " + token, csq, pos.getIndex()); 706 } 707 token = nextToken(csq, pos); 708 } 709 } 710 711 private static Token nextToken(CharSequence csq, ParsePosition pos) { 712 final int length = csq.length(); 713 while (pos.getIndex() < length) { 714 char c = csq.charAt(pos.getIndex()); 715 if (isUnitIdentifierPart(c)) { 716 return Token.IDENTIFIER; 717 } else if (c == '(') { 718 return Token.OPEN_PAREN; 719 } else if (c == ')') { 720 return Token.CLOSE_PAREN; 721 } else if ((c == '^') || (c == '\u00b9') || (c == '\u00b2') || (c == '\u00b3')) { 722 return Token.EXPONENT; 723 } else if (c == '*') { 724 if (csq.length() == pos.getIndex() + 1) { 725 throw new MeasurementParseException("unexpected token " + Token.EOF, csq, pos.getIndex()); // return ; 726 } 727 char c2 = csq.charAt(pos.getIndex() + 1); 728 return c2 == '*' ? Token.EXPONENT : Token.MULTIPLY; 729 } else if (c == MIDDLE_DOT) { 730 return Token.MULTIPLY; 731 } else if (c == '/') { 732 return Token.DIVIDE; 733 } else if (c == '+') { 734 return Token.PLUS; 735 } else if ((c == '-') || Character.isDigit(c)) { 736 int index = pos.getIndex() + 1; 737 while ((index < length) && (Character.isDigit(c) || (c == '-') || (c == '.') || (c == 'E'))) { 738 c = csq.charAt(index++); 739 if (c == '.') { 740 return Token.FLOAT; 741 } 742 } 743 return Token.INTEGER; 744 } 745 pos.setIndex(pos.getIndex() + 1); 746 } 747 return Token.EOF; 748 } 749 750 private static void check(boolean expr, String message, CharSequence csq, int index) throws MeasurementParseException { 751 if (!expr) { 752 throw new MeasurementParseException(message + " (in " + csq + " at index " + index + ")", index); 753 } 754 } 755 756 private static Exponent readExponent(CharSequence csq, ParsePosition pos) { 757 char c = csq.charAt(pos.getIndex()); 758 if (c == '^') { 759 pos.setIndex(pos.getIndex() + 1); 760 } else if (c == '*') { 761 pos.setIndex(pos.getIndex() + 2); 762 } 763 final int length = csq.length(); 764 int pow = 0; 765 boolean isPowNegative = false; 766 boolean parseRoot = false; 767 768 POWERLOOP: while (pos.getIndex() < length) { 769 c = csq.charAt(pos.getIndex()); 770 switch(c) { 771 case '-': isPowNegative = true; break; 772 case '\u00b9': pow = pow * 10 + 1; break; 773 case '\u00b2': pow = pow * 10 + 2; break; 774 case '\u00b3': pow = pow * 10 + 3; break; 775 case ':': parseRoot = true; break POWERLOOP; 776 default: 777 if (c >= '0' && c <= '9') pow = pow * 10 + (c - '0'); 778 else break POWERLOOP; 779 } 780 pos.setIndex(pos.getIndex() + 1); 781 } 782 if (pow == 0) pow = 1; 783 784 int root = 0; 785 boolean isRootNegative = false; 786 if (parseRoot) { 787 pos.setIndex(pos.getIndex() + 1); 788 ROOTLOOP: while (pos.getIndex() < length) { 789 c = csq.charAt(pos.getIndex()); 790 switch(c) { 791 case '-': isRootNegative = true; break; 792 case '\u00b9': root = root * 10 + 1; break; 793 case '\u00b2': root = root * 10 + 2; break; 794 case '\u00b3': root = root * 10 + 3; break; 795 default: 796 if (c >= '0' && c <= '9') root = root * 10 + (c - '0'); 797 else break ROOTLOOP; 798 } 799 pos.setIndex(pos.getIndex() + 1); 800 } 801 } 802 if (root == 0) root = 1; 803 804 return new Exponent(isPowNegative ? -pow : pow, isRootNegative ? -root : root); 805 } 806 807 private static long readLong(CharSequence csq, ParsePosition pos) { 808 final int length = csq.length(); 809 int result = 0; 810 boolean isNegative = false; 811 while (pos.getIndex() < length) { 812 char c = csq.charAt(pos.getIndex()); 813 if (c == '-') { 814 isNegative = true; 815 } else if ((c >= '0') && (c <= '9')) { 816 result = result * 10 + (c - '0'); 817 } else { 818 break; 819 } 820 pos.setIndex(pos.getIndex() + 1); 821 } 822 return isNegative ? -result : result; 823 } 824 825 private static double readDouble(CharSequence csq, ParsePosition pos) { 826 final int length = csq.length(); 827 int start = pos.getIndex(); 828 int end = start + 1; 829 while (end < length) { 830 if ("0123456789+-.E".indexOf(csq.charAt(end)) < 0) { 831 break; 832 } 833 end += 1; 834 } 835 pos.setIndex(end + 1); 836 return Double.parseDouble(csq.subSequence(start, end).toString()); 837 } 838 839 private static String readIdentifier(CharSequence csq, ParsePosition pos) { 840 final int length = csq.length(); 841 int start = pos.getIndex(); 842 int i = start; 843 while ((++i < length) && isUnitIdentifierPart(csq.charAt(i))) { 844 } 845 pos.setIndex(i); 846 return csq.subSequence(start, i).toString(); 847 } 848 849 // ////////////////////////// 850 // Formatting. 851 852 @Override 853 public Appendable format(Unit<?> unit, Appendable appendable) throws IOException { 854 String name = nameFor(unit); 855 if (name != null) { 856 return appendable.append(name); 857 } 858 if (!(unit instanceof ProductUnit)) { 859 throw new IllegalArgumentException("Cannot format given Object as a Unit"); 860 } 861 862 // Product unit. 863 ProductUnit<?> productUnit = (ProductUnit<?>) unit; 864 865 // Special case: self-powered product unit 866 if (productUnit.getUnitCount() == 1 && productUnit.getUnit(0) instanceof ProductUnit) { 867 final ProductUnit<?> powerUnit = (ProductUnit<?>) productUnit.getUnit(0); 868 // is the sub-unit known under a given label? 869 if (nameFor(powerUnit) == null) 870 // apply the power to the sub-units and format those instead 871 return format(ProductUnit.ofPow(powerUnit, productUnit.getUnitPow(0)), appendable); 872 } 873 874 int invNbr = 0; 875 876 // Write positive exponents first. 877 boolean start = true; 878 for (int i = 0; i < productUnit.getUnitCount(); i++) { 879 int pow = productUnit.getUnitPow(i); 880 if (pow >= 0) { 881 if (!start) { 882 appendable.append(MIDDLE_DOT); // Separator. 883 } 884 name = nameFor(productUnit.getUnit(i)); 885 int root = productUnit.getUnitRoot(i); 886 append(appendable, name, pow, root); 887 start = false; 888 } else { 889 invNbr++; 890 } 891 } 892 893 // Write negative exponents. 894 if (invNbr != 0) { 895 if (start) { 896 appendable.append('1'); // e.g. 1/s 897 } 898 appendable.append('/'); 899 if (invNbr > 1) { 900 appendable.append('('); 901 } 902 start = true; 903 for (int i = 0; i < productUnit.getUnitCount(); i++) { 904 int pow = productUnit.getUnitPow(i); 905 if (pow < 0) { 906 name = nameFor(productUnit.getUnit(i)); 907 int root = productUnit.getUnitRoot(i); 908 if (!start) { 909 appendable.append(MIDDLE_DOT); // Separator. 910 } 911 append(appendable, name, -pow, root); 912 start = false; 913 } 914 } 915 if (invNbr > 1) { 916 appendable.append(')'); 917 } 918 } 919 return appendable; 920 } 921 922 private static void append(Appendable appendable, CharSequence symbol, int pow, int root) throws IOException { 923 appendable.append(symbol); 924 if ((pow != 1) || (root != 1)) { 925 // Write exponent. 926 if ((pow == 2) && (root == 1)) { 927 appendable.append('\u00b2'); // Square 928 } else if ((pow == 3) && (root == 1)) { 929 appendable.append('\u00b3'); // Cubic 930 } else { 931 // Use general exponent form. 932 appendable.append('^'); 933 appendable.append(String.valueOf(pow)); 934 if (root != 1) { 935 appendable.append(':'); 936 appendable.append(String.valueOf(root)); 937 } 938 } 939 } 940 } 941 942 // private static final long serialVersionUID = 1L; 943 944 @Override 945 public Unit<?> parse(CharSequence csq) throws MeasurementParseException { 946 return parse(csq, 0); 947 } 948 949 protected Unit<?> parse(CharSequence csq, int index) throws IllegalArgumentException { 950 return parse(csq, new ParsePosition(index)); 951 } 952 953 @Override 954 public Unit<?> parse(CharSequence csq, ParsePosition cursor) throws IllegalArgumentException { 955 return parseObject(csq.toString(), cursor); 956 } 957 } 958 959 /** 960 * This class represents the ASCII format. 961 */ 962 protected final static class ASCIIFormat extends DefaultFormat { 963 964 private ASCIIFormat() { 965 super(); 966 } 967 968 private ASCIIFormat init() { 969 970 // ASCII 971 for (int i = 0; i < METRIC_UNITS.length; i++) { 972 Unit<?> si = METRIC_UNITS[i]; 973 String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si).getSymbol() : ((AlternateUnit<?>) si).getSymbol(); 974 if (isAllASCII(symbol)) 975 label(si, symbol); 976 for (int j = 0; j < METRIC_PREFIX_SYMBOLS.length; j++) { 977 Unit<?> u = si.prefix(MetricPrefix.values()[j]); 978 if ( "µ".equals(METRIC_PREFIX_SYMBOLS[j]) ) { 979 label(u, "micro" + asciiSymbol(symbol)); 980 } 981 } // TODO what about BINARY_PREFIX here? 982 } 983 984 // -- GRAM/KILOGRAM 985 986 label(Units.GRAM, "g"); 987 for(MetricPrefix prefix : MetricPrefix.values()) { 988 switch (prefix) { 989 case KILO: 990 label(Units.KILOGRAM, "kg"); 991 break; 992 case MICRO: 993 label(MICRO(Units.GRAM), "microg"); // instead of 'µg' -> 'microg' 994 break; 995 default: 996 label(Units.GRAM.prefix(prefix), prefix.getSymbol()+"g"); 997 break; 998 } 999 } 1000 1001 // Alias and ASCIIFormat for Ohm 1002 label(Units.OHM, "Ohm"); 1003 for (int i = 0; i < METRIC_PREFIX_SYMBOLS.length; i++) { 1004 label(Units.OHM.prefix(MetricPrefix.values()[i]), asciiPrefix(METRIC_PREFIX_SYMBOLS[i]) + "Ohm"); 1005 } 1006 1007 // Special case for DEGREE_CELSIUS. 1008 label(Units.CELSIUS, "Celsius"); 1009 for (int i = 0; i < METRIC_PREFIX_SYMBOLS.length; i++) { 1010 label(Units.CELSIUS.prefix(MetricPrefix.values()[i]), asciiPrefix(METRIC_PREFIX_SYMBOLS[i]) + "Celsius"); 1011 } 1012 alias(Units.CELSIUS, "Cel"); 1013 1014 label(Units.METRE, "m"); 1015 label(Units.SECOND, "s"); 1016 label(Units.KILOMETRE_PER_HOUR, "km/h"); 1017 alias(Units.SQUARE_METRE, "m2"); 1018 alias(Units.CUBIC_METRE, "m3"); 1019 1020 // -- LITRE 1021 1022 label(Units.LITRE, "l"); 1023 for(Prefix prefix : MetricPrefix.values()) { 1024 if(prefix==MICRO) { 1025 label(MICRO(Units.LITRE), "microL"); // instead of 'µL' -> 'microL' 1026 } else { 1027 label(Units.LITRE.prefix(prefix), prefix.getSymbol()+"L"); 1028 } 1029 } 1030 label(Units.NEWTON, "N"); 1031 label(Units.RADIAN, "rad"); 1032 1033 label(AbstractUnit.ONE, "one"); 1034 1035 return this; 1036 } 1037 1038 1039 @Override 1040 protected String nameFor(Unit<?> unit) { 1041 // First search if specific ASCII name should be used. 1042 String name = unitToName.get(unit); 1043 if (name != null) 1044 return name; 1045 // Else returns default name. 1046 return DEFAULT.nameFor(unit); 1047 } 1048 1049 @Override 1050 protected Unit<?> unitFor(String name) { 1051 // First search if specific ASCII name. 1052 Unit<?> unit = nameToUnit.get(name); 1053 if (unit != null) 1054 return unit; 1055 // Else returns default mapping. 1056 return DEFAULT.unitFor(name); 1057 } 1058 1059 @Override 1060 public String toString() { 1061 return "SimpleUnitFormat - ASCII"; 1062 } 1063 1064 @Override 1065 public Appendable format(Unit<?> unit, Appendable appendable) throws IOException { 1066 String name = nameFor(unit); 1067 if (name != null) 1068 return appendable.append(name); 1069 if (!(unit instanceof ProductUnit)) 1070 throw new IllegalArgumentException("Cannot format given Object as a Unit"); 1071 1072 ProductUnit<?> productUnit = (ProductUnit<?>) unit; 1073 for (int i = 0; i < productUnit.getUnitCount(); i++) { 1074 if (i != 0) { 1075 appendable.append('*'); // Separator. 1076 } 1077 name = nameFor(productUnit.getUnit(i)); 1078 int pow = productUnit.getUnitPow(i); 1079 int root = productUnit.getUnitRoot(i); 1080 appendable.append(name); 1081 if ((pow != 1) || (root != 1)) { 1082 // Use general exponent form. 1083 appendable.append('^'); 1084 appendable.append(String.valueOf(pow)); 1085 if (root != 1) { 1086 appendable.append(':'); 1087 appendable.append(String.valueOf(root)); 1088 } 1089 } 1090 } 1091 return appendable; 1092 } 1093 1094 @Override 1095 protected boolean isValidIdentifier(String name) { 1096 if ((name == null) || (name.length() == 0)) 1097 return false; 1098 // label must not begin with a digit or mathematical operator 1099 return isUnitIdentifierPart(name.charAt(0)) && isAllASCII(name); 1100 /* 1101 * for (int i = 0; i < name.length(); i++) { if 1102 * (!isAsciiCharacter(name.charAt(i))) return false; } return true; 1103 */ 1104 } 1105 } 1106 1107 private static String asciiPrefix(String prefix) { 1108 return "µ".equals(prefix) ? "micro" : prefix; 1109 } 1110 1111 private static String asciiSymbol(String s) { 1112 return "Ω".equals(s) ? "Ohm" : s; 1113 } 1114 1115 /** to check if a string only contains US-ASCII characters */ 1116 private static boolean isAllASCII(String input) { 1117 boolean isASCII = true; 1118 for (int i = 0; i < input.length(); i++) { 1119 int c = input.charAt(i); 1120 if (c > 0x7F) { 1121 isASCII = false; 1122 break; 1123 } 1124 } 1125 return isASCII; 1126 } 1127 1128 1129}