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