| 1 | /* |
| 2 | * JScience - Java(TM) Tools and Libraries for the Advancement of Sciences. |
| 3 | * Copyright (C) 2006 - JScience (http://jscience.org/) |
| 4 | * All rights reserved. |
| 5 | * |
| 6 | * Permission to use, copy, modify, and distribute this software is |
| 7 | * freely granted, provided that this notice is preserved. |
| 8 | */ |
| 9 | package javax.measure.unit; |
| 10 | |
| 11 | import java.io.IOException; |
| 12 | import java.lang.CharSequence; |
| 13 | import java.text.FieldPosition; |
| 14 | import java.text.Format; |
| 15 | import java.text.ParseException; |
| 16 | import java.text.ParsePosition; |
| 17 | import java.util.HashMap; |
| 18 | import java.util.Locale; |
| 19 | //@RETROWEAVER import javolution.text.Appendable; |
| 20 | import javax.measure.converter.AddConverter; |
| 21 | import javax.measure.converter.MultiplyConverter; |
| 22 | import javax.measure.converter.RationalConverter; |
| 23 | import javax.measure.converter.UnitConverter; |
| 24 | import javax.measure.quantity.Quantity; |
| 25 | |
| 26 | import static javax.measure.unit.SI.*; |
| 27 | |
| 28 | /** |
| 29 | * <p> This class provides the interface for formatting and parsing {@link |
| 30 | * Unit units}.</p> |
| 31 | * |
| 32 | * <p> For all {@link SI} units, the 20 SI prefixes used to form decimal |
| 33 | * multiples and sub-multiples of SI units are recognized. |
| 34 | * {@link NonSI} units are directly recognized. For example:[code] |
| 35 | * Unit.valueOf("m°C").equals(SI.MILLI(SI.CELSIUS)) |
| 36 | * Unit.valueOf("kW").equals(SI.KILO(SI.WATT)) |
| 37 | * Unit.valueOf("ft").equals(SI.METER.multiply(0.3048))[/code]</p> |
| 38 | * |
| 39 | * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a> |
| 40 | * @author Eric Russell |
| 41 | * @version 1.3, August 29, 2006 |
| 42 | */ |
| 43 | public abstract class UnitFormat extends Format { |
| 44 | |
| 45 | /** |
| 46 | * Holds the standard unit format. |
| 47 | */ |
| 48 | private static final DefaultFormat DEFAULT = new DefaultFormat(); |
| 49 | |
| 50 | /** |
| 51 | * Holds the ASCIIFormat unit format. |
| 52 | */ |
| 53 | private static final ASCIIFormat ASCII = new ASCIIFormat(); |
| 54 | |
| 55 | /** |
| 56 | * Returns the unit format for the default locale (format used by |
| 57 | * {@link Unit#valueOf(CharSequence) Unit.valueOf(CharSequence)} and |
| 58 | * {@link Unit#toString() Unit.toString()}). |
| 59 | * |
| 60 | * @return the default unit format (locale sensitive). |
| 61 | */ |
| 62 | public static UnitFormat getInstance() { |
| 63 | return UnitFormat.getInstance(Locale.getDefault()); |
| 64 | } |
| 65 | |
| 66 | /** |
| 67 | * Returns the unit format for the specified locale. |
| 68 | * |
| 69 | * @return the unit format for the specified locale. |
| 70 | */ |
| 71 | public static UnitFormat getInstance(Locale inLocale) { |
| 72 | return DEFAULT; // TBD: Implement Locale Format. |
| 73 | } |
| 74 | |
| 75 | /** |
| 76 | * Returns the <a href="http://aurora.regenstrief.org/UCUM/ucum.html">UCUM |
| 77 | * </a> international unit format; this format uses characters range |
| 78 | * <code>0000-007F</code> exclusively and <b>is not</b> locale-sensitive. |
| 79 | * For example: <code>kg.m/s2</code> |
| 80 | * |
| 81 | * @return the UCUM international format. |
| 82 | */ |
| 83 | public static UnitFormat getUCUMInstance() { |
| 84 | return UnitFormat.ASCII; // TBD - Provide UCUM implementation. |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Base constructor. |
| 89 | */ |
| 90 | protected UnitFormat() { |
| 91 | } |
| 92 | |
| 93 | /** |
| 94 | * Formats the specified unit. |
| 95 | * |
| 96 | * @param unit the unit to format. |
| 97 | * @param appendable the appendable destination. |
| 98 | * @throws IOException if an error occurs. |
| 99 | */ |
| 100 | public abstract Appendable format(Unit<?> unit, Appendable appendable) |
| 101 | throws IOException; |
| 102 | |
| 103 | /** |
| 104 | * Parses a sequence of character to produce a unit or a rational product |
| 105 | * of unit. |
| 106 | * |
| 107 | * @param csq the <code>CharSequence</code> to parse. |
| 108 | * @param pos an object holding the parsing index and error position. |
| 109 | * @return an {@link Unit} parsed from the character sequence. |
| 110 | * @throws IllegalArgumentException if the character sequence contains |
| 111 | * an illegal syntax. |
| 112 | */ |
| 113 | public abstract Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) |
| 114 | throws ParseException; |
| 115 | |
| 116 | /** |
| 117 | * Parses a sequence of character to produce a single unit. |
| 118 | * |
| 119 | * @param csq the <code>CharSequence</code> to parse. |
| 120 | * @param pos an object holding the parsing index and error position. |
| 121 | * @return an {@link Unit} parsed from the character sequence. |
| 122 | * @throws IllegalArgumentException if the character sequence does not contain |
| 123 | * a valid unit identifier. |
| 124 | */ |
| 125 | public abstract Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) |
| 126 | throws ParseException; |
| 127 | |
| 128 | /** |
| 129 | * Attaches a system-wide label to the specified unit. For example: |
| 130 | * [code] |
| 131 | * UnitFormat.getInstance().label(DAY.multiply(365), "year"); |
| 132 | * UnitFormat.getInstance().label(METER.multiply(0.3048), "ft"); |
| 133 | * [/code] |
| 134 | * If the specified label is already associated to an unit the previous |
| 135 | * association is discarded or ignored. |
| 136 | * |
| 137 | * @param unit the unit being labelled. |
| 138 | * @param label the new label for this unit. |
| 139 | * @throws IllegalArgumentException if the label is not a |
| 140 | * {@link UnitFormat#isValidIdentifier(String)} valid identifier. |
| 141 | */ |
| 142 | public abstract void label(Unit<?> unit, String label); |
| 143 | |
| 144 | /** |
| 145 | * Attaches a system-wide alias to this unit. Multiple aliases may |
| 146 | * be attached to the same unit. Aliases are used during parsing to |
| 147 | * recognize different variants of the same unit. For example: |
| 148 | * [code] |
| 149 | * UnitFormat.getLocaleInstance().alias(METER.multiply(0.3048), "foot"); |
| 150 | * UnitFormat.getLocaleInstance().alias(METER.multiply(0.3048), "feet"); |
| 151 | * UnitFormat.getLocaleInstance().alias(METER, "meter"); |
| 152 | * UnitFormat.getLocaleInstance().alias(METER, "metre"); |
| 153 | * [/code] |
| 154 | * If the specified label is already associated to an unit the previous |
| 155 | * association is discarded or ignored. |
| 156 | * |
| 157 | * @param unit the unit being aliased. |
| 158 | * @param alias the alias attached to this unit. |
| 159 | * @throws IllegalArgumentException if the label is not a |
| 160 | * {@link UnitFormat#isValidIdentifier(String)} valid identifier. |
| 161 | */ |
| 162 | public abstract void alias(Unit<?> unit, String alias); |
| 163 | |
| 164 | /** |
| 165 | * Indicates if the specified name can be used as unit identifier. |
| 166 | * |
| 167 | * @param name the identifier to be tested. |
| 168 | * @return <code>true</code> if the name specified can be used as |
| 169 | * label or alias for this format;<code>false</code> otherwise. |
| 170 | */ |
| 171 | public abstract boolean isValidIdentifier(String name); |
| 172 | |
| 173 | /** |
| 174 | * Formats an unit and appends the resulting text to a given string |
| 175 | * buffer (implements <code>java.text.Format</code>). |
| 176 | * |
| 177 | * @param unit the unit to format. |
| 178 | * @param toAppendTo where the text is to be appended |
| 179 | * @param pos the field position (not used). |
| 180 | * @return <code>toAppendTo</code> |
| 181 | */ |
| 182 | public final StringBuffer format(Object unit, final StringBuffer toAppendTo, |
| 183 | FieldPosition pos) { |
| 184 | try { |
| 185 | Object dest = toAppendTo; |
| 186 | if (dest instanceof Appendable) { |
| 187 | format((Unit<?>) unit, (Appendable)dest); |
| 188 | } else { // When retroweaver is used to produce 1.4 binaries. |
| 189 | format((Unit<?>) unit, new Appendable() { |
| 190 | |
| 191 | public Appendable append(char arg0) throws IOException { |
| 192 | toAppendTo.append(arg0); |
| 193 | return null; |
| 194 | } |
| 195 | |
| 196 | public Appendable append(CharSequence arg0) throws IOException { |
| 197 | toAppendTo.append(arg0); |
| 198 | return null; |
| 199 | } |
| 200 | |
| 201 | public Appendable append(CharSequence arg0, int arg1, int arg2) throws IOException { |
| 202 | toAppendTo.append(arg0.subSequence(arg1, arg2)); |
| 203 | return null; |
| 204 | }}); |
| 205 | } |
| 206 | return toAppendTo; |
| 207 | } catch (IOException e) { |
| 208 | throw new Error(e); // Should never happen. |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | /** |
| 213 | * Parses the text from a string to produce an object |
| 214 | * (implements <code>java.text.Format</code>). |
| 215 | * |
| 216 | * @param source the string source, part of which should be parsed. |
| 217 | * @param pos the cursor position. |
| 218 | * @return the corresponding unit or <code>null</code> if the string |
| 219 | * cannot be parsed. |
| 220 | */ |
| 221 | public final Unit<?> parseObject(String source, ParsePosition pos) { |
| 222 | int start = pos.getIndex(); |
| 223 | try { |
| 224 | return parseProductUnit(source, pos); |
| 225 | } catch (ParseException e) { |
| 226 | pos.setIndex(start); |
| 227 | pos.setErrorIndex(e.getErrorOffset()); |
| 228 | return null; |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | |
| 233 | /** |
| 234 | * This class represents an exponent with both a power (numerator) |
| 235 | * and a root (denominator). |
| 236 | */ |
| 237 | private static class Exponent { |
| 238 | public final int pow; |
| 239 | public final int root; |
| 240 | public Exponent (int pow, int root) { |
| 241 | this.pow = pow; |
| 242 | this.root = root; |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * This class represents the standard format. |
| 248 | */ |
| 249 | protected static class DefaultFormat extends UnitFormat { |
| 250 | |
| 251 | /** |
| 252 | * Holds the name to unit mapping. |
| 253 | */ |
| 254 | final HashMap<String, Unit<?>> _nameToUnit = new HashMap<String, Unit<?>>(); |
| 255 | |
| 256 | /** |
| 257 | * Holds the unit to name mapping. |
| 258 | */ |
| 259 | final HashMap<Unit<?>, String> _unitToName = new HashMap<Unit<?>, String>(); |
| 260 | |
| 261 | @Override |
| 262 | public void label(Unit<?> unit, String label) { |
| 263 | if (!isValidIdentifier(label)) |
| 264 | throw new IllegalArgumentException("Label: " + label |
| 265 | + " is not a valid identifier."); |
| 266 | synchronized (this) { |
| 267 | _nameToUnit.put(label, unit); |
| 268 | _unitToName.put(unit, label); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | @Override |
| 273 | public void alias(Unit<?> unit, String alias) { |
| 274 | if (!isValidIdentifier(alias)) |
| 275 | throw new IllegalArgumentException("Alias: " + alias |
| 276 | + " is not a valid identifier."); |
| 277 | synchronized (this) { |
| 278 | _nameToUnit.put(alias, unit); |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | @Override |
| 283 | public boolean isValidIdentifier(String name) { |
| 284 | if ((name == null) || (name.length() == 0)) |
| 285 | return false; |
| 286 | for (int i = 0; i < name.length(); i++) { |
| 287 | if (!isUnitIdentifierPart(name.charAt(i))) |
| 288 | return false; |
| 289 | } |
| 290 | return true; |
| 291 | } |
| 292 | |
| 293 | static boolean isUnitIdentifierPart(char ch) { |
| 294 | return Character.isLetter(ch) || |
| 295 | (!Character.isWhitespace(ch) && !Character.isDigit(ch) |
| 296 | && (ch != '·') && (ch != '*') && (ch != '/') |
| 297 | && (ch != '(') && (ch != ')') && (ch != '[') && (ch != ']') |
| 298 | && (ch != '¹') && (ch != '²') && (ch != '³') |
| 299 | && (ch != '^') && (ch != '+') && (ch != '-')); |
| 300 | } |
| 301 | |
| 302 | // Returns the name for the specified unit or null if product unit. |
| 303 | public String nameFor(Unit<?> unit) { |
| 304 | // Searches label database. |
| 305 | String label = _unitToName.get(unit); |
| 306 | if (label != null) |
| 307 | return label; |
| 308 | if (unit instanceof BaseUnit) |
| 309 | return ((BaseUnit<?>) unit).getSymbol(); |
| 310 | if (unit instanceof AlternateUnit) |
| 311 | return ((AlternateUnit<?>) unit).getSymbol(); |
| 312 | if (unit instanceof TransformedUnit) { |
| 313 | TransformedUnit<?> tfmUnit = (TransformedUnit<?>) unit; |
| 314 | Unit<?> baseUnits = tfmUnit.getStandardUnit(); |
| 315 | UnitConverter cvtr = tfmUnit.toStandardUnit(); |
| 316 | StringBuffer result = new StringBuffer(); |
| 317 | String baseUnitName = baseUnits.toString(); |
| 318 | if ((baseUnitName.indexOf('·') >= 0) || |
| 319 | (baseUnitName.indexOf('*') >= 0) || |
| 320 | (baseUnitName.indexOf('/') >= 0)) { |
| 321 | // We could use parentheses whenever baseUnits is an |
| 322 | // instanceof ProductUnit, but most ProductUnits have aliases, |
| 323 | // so we'd end up with a lot of unnecessary parentheses. |
| 324 | result.append('('); |
| 325 | result.append(baseUnitName); |
| 326 | result.append(')'); |
| 327 | } else { |
| 328 | result.append(baseUnitName); |
| 329 | } |
| 330 | if (cvtr instanceof AddConverter) { |
| 331 | result.append('+'); |
| 332 | result.append(((AddConverter) cvtr).getOffset()); |
| 333 | } else if (cvtr instanceof RationalConverter) { |
| 334 | long dividend = ((RationalConverter) cvtr).getDividend(); |
| 335 | if (dividend != 1) { |
| 336 | result.append('*'); |
| 337 | result.append(dividend); |
| 338 | } |
| 339 | long divisor = ((RationalConverter) cvtr).getDivisor(); |
| 340 | if (divisor != 1) { |
| 341 | result.append('/'); |
| 342 | result.append(divisor); |
| 343 | } ; |
| 344 | } else if (cvtr instanceof MultiplyConverter) { |
| 345 | result.append('*'); |
| 346 | result.append(((MultiplyConverter) cvtr).getFactor()); |
| 347 | } else { // Other converters. |
| 348 | return "[" + baseUnits + "?]"; |
| 349 | } |
| 350 | return result.toString(); |
| 351 | } |
| 352 | // Compound unit. |
| 353 | if (unit instanceof CompoundUnit) { |
| 354 | CompoundUnit<?> cpdUnit = (CompoundUnit<?>) unit; |
| 355 | return nameFor(cpdUnit.getHigher()).toString() + ":" |
| 356 | + nameFor(cpdUnit.getLower()); |
| 357 | } |
| 358 | return null; // Product unit. |
| 359 | } |
| 360 | |
| 361 | // Returns the unit for the specified name. |
| 362 | public Unit<?> unitFor(String name) { |
| 363 | Unit<?> unit = _nameToUnit.get(name); |
| 364 | if (unit != null) |
| 365 | return unit; |
| 366 | unit = Unit.SYMBOL_TO_UNIT.get(name); |
| 367 | return unit; |
| 368 | } |
| 369 | |
| 370 | //////////////////////////// |
| 371 | // Parsing. |
| 372 | |
| 373 | @SuppressWarnings("unchecked") |
| 374 | public Unit<? extends Quantity> parseSingleUnit(CharSequence csq, ParsePosition pos) |
| 375 | throws ParseException { |
| 376 | int startIndex = pos.getIndex(); |
| 377 | String name = readIdentifier(csq, pos); |
| 378 | Unit unit = unitFor(name); |
| 379 | check(unit != null, name + " not recognized", csq, startIndex); |
| 380 | return unit; |
| 381 | } |
| 382 | |
| 383 | @SuppressWarnings("unchecked") |
| 384 | @Override |
| 385 | public Unit<? extends Quantity> parseProductUnit(CharSequence csq, ParsePosition pos) |
| 386 | throws ParseException { |
| 387 | Unit result = Unit.ONE; |
| 388 | int token = nextToken(csq, pos); |
| 389 | switch (token) { |
| 390 | case IDENTIFIER: |
| 391 | result = parseSingleUnit(csq, pos); |
| 392 | break; |
| 393 | case OPEN_PAREN: |
| 394 | pos.setIndex(pos.getIndex() + 1); |
| 395 | result = parseProductUnit(csq, pos); |
| 396 | token = nextToken(csq, pos); |
| 397 | check(token == CLOSE_PAREN, "')' expected", csq, pos.getIndex()); |
| 398 | pos.setIndex(pos.getIndex() + 1); |
| 399 | break; |
| 400 | } |
| 401 | token = nextToken(csq, pos); |
| 402 | while (true) { |
| 403 | switch (token) { |
| 404 | case EXPONENT: |
| 405 | Exponent e = readExponent(csq, pos); |
| 406 | if (e.pow != 1) { |
| 407 | result = result.pow(e.pow); |
| 408 | } |
| 409 | if (e.root != 1) { |
| 410 | result = result.root(e.root); |
| 411 | } |
| 412 | break; |
| 413 | case MULTIPLY: |
| 414 | pos.setIndex(pos.getIndex() + 1); |
| 415 | token = nextToken(csq, pos); |
| 416 | if (token == INTEGER) { |
| 417 | long n = readLong(csq, pos); |
| 418 | if (n != 1) { |
| 419 | result = result.times(n); |
| 420 | } |
| 421 | } else if (token == FLOAT) { |
| 422 | double d = readDouble(csq, pos); |
| 423 | if (d != 1.0) { |
| 424 | result = result.times(d); |
| 425 | } |
| 426 | } else { |
| 427 | result = result.times(parseProductUnit(csq, pos)); |
| 428 | } |
| 429 | break; |
| 430 | case DIVIDE: |
| 431 | pos.setIndex(pos.getIndex() + 1); |
| 432 | token = nextToken(csq, pos); |
| 433 | if (token == INTEGER) { |
| 434 | long n = readLong(csq, pos); |
| 435 | if (n != 1) { |
| 436 | result = result.divide(n); |
| 437 | } |
| 438 | } else if (token == FLOAT) { |
| 439 | double d = readDouble(csq, pos); |
| 440 | if (d != 1.0) { |
| 441 | result = result.divide(d); |
| 442 | } |
| 443 | } else { |
| 444 | result = result.divide(parseProductUnit(csq, pos)); |
| 445 | } |
| 446 | break; |
| 447 | case PLUS: |
| 448 | pos.setIndex(pos.getIndex() + 1); |
| 449 | token = nextToken(csq, pos); |
| 450 | if (token == INTEGER) { |
| 451 | long n = readLong(csq, pos); |
| 452 | if (n != 1) { |
| 453 | result = result.plus(n); |
| 454 | } |
| 455 | } else if (token == FLOAT) { |
| 456 | double d = readDouble(csq, pos); |
| 457 | if (d != 1.0) { |
| 458 | result = result.plus(d); |
| 459 | } |
| 460 | } else { |
| 461 | throw new ParseException("not a number", pos.getIndex()); |
| 462 | } |
| 463 | break; |
| 464 | case EOF: |
| 465 | case CLOSE_PAREN: |
| 466 | return result; |
| 467 | default: |
| 468 | throw new ParseException("unexpected token " + token, pos.getIndex()); |
| 469 | } |
| 470 | token = nextToken(csq, pos); |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | private static final int EOF = 0; |
| 475 | private static final int IDENTIFIER = 1; |
| 476 | private static final int OPEN_PAREN= 2; |
| 477 | private static final int CLOSE_PAREN= 3; |
| 478 | private static final int EXPONENT = 4; |
| 479 | private static final int MULTIPLY = 5; |
| 480 | private static final int DIVIDE = 6; |
| 481 | private static final int PLUS = 7; |
| 482 | private static final int INTEGER = 8; |
| 483 | private static final int FLOAT = 9; |
| 484 | |
| 485 | private int nextToken(CharSequence csq, ParsePosition pos) { |
| 486 | final int length = csq.length(); |
| 487 | while (pos.getIndex() < length) { |
| 488 | char c = csq.charAt(pos.getIndex()); |
| 489 | if (isUnitIdentifierPart(c)) { |
| 490 | return IDENTIFIER; |
| 491 | } else if (c == '(') { |
| 492 | return OPEN_PAREN; |
| 493 | } else if (c == ')') { |
| 494 | return CLOSE_PAREN; |
| 495 | } else if ((c == '^') || (c == '¹') || (c == '²') || (c == '³')) { |
| 496 | return EXPONENT; |
| 497 | } else if (c == '*') { |
| 498 | char c2 = csq.charAt(pos.getIndex() + 1); |
| 499 | if (c2 == '*') { |
| 500 | return EXPONENT; |
| 501 | } else { |
| 502 | return MULTIPLY; |
| 503 | } |
| 504 | } else if (c == '·') { |
| 505 | return MULTIPLY; |
| 506 | } else if (c == '/') { |
| 507 | return DIVIDE; |
| 508 | } else if (c == '+') { |
| 509 | return PLUS; |
| 510 | } else if ((c == '-') || Character.isDigit(c)) { |
| 511 | int index = pos.getIndex()+1; |
| 512 | while ((index < length) && |
| 513 | (Character.isDigit(c) || (c == '-') || (c == '.') || (c == 'E'))) { |
| 514 | c = csq.charAt(index++); |
| 515 | if (c == '.') { |
| 516 | return FLOAT; |
| 517 | } |
| 518 | } |
| 519 | return INTEGER; |
| 520 | } |
| 521 | pos.setIndex(pos.getIndex() + 1); |
| 522 | } |
| 523 | return EOF; |
| 524 | } |
| 525 | |
| 526 | private void check(boolean expr, String message, CharSequence csq, |
| 527 | int index) throws ParseException { |
| 528 | if (!expr) { |
| 529 | throw new ParseException(message + " (in " + csq |
| 530 | + " at index " + index + ")", index); |
| 531 | } |
| 532 | } |
| 533 | |
| 534 | private Exponent readExponent (CharSequence csq, ParsePosition pos) { |
| 535 | char c = csq.charAt(pos.getIndex()); |
| 536 | if (c == '^') { |
| 537 | pos.setIndex(pos.getIndex()+1); |
| 538 | } else if (c == '*') { |
| 539 | pos.setIndex(pos.getIndex()+2); |
| 540 | } |
| 541 | final int length = csq.length(); |
| 542 | int pow = 0; |
| 543 | boolean isPowNegative = false; |
| 544 | int root = 0; |
| 545 | boolean isRootNegative = false; |
| 546 | boolean isRoot = false; |
| 547 | while (pos.getIndex() < length) { |
| 548 | c = csq.charAt(pos.getIndex()); |
| 549 | if (c == '¹') { |
| 550 | if (isRoot) { |
| 551 | root = root * 10 + 1; |
| 552 | } else { |
| 553 | pow = pow * 10 + 1; |
| 554 | } |
| 555 | } else if (c == '²') { |
| 556 | if (isRoot) { |
| 557 | root = root * 10 + 2; |
| 558 | } else { |
| 559 | pow = pow * 10 + 2; |
| 560 | } |
| 561 | } else if (c == '³') { |
| 562 | if (isRoot) { |
| 563 | root = root * 10 + 3; |
| 564 | } else { |
| 565 | pow = pow * 10 + 3; |
| 566 | } |
| 567 | } else if (c == '-') { |
| 568 | if (isRoot) { |
| 569 | isRootNegative = true; |
| 570 | } else { |
| 571 | isPowNegative = true; |
| 572 | } |
| 573 | } else if ((c >= '0') && (c <= '9')) { |
| 574 | if (isRoot) { |
| 575 | root = root * 10 + (c - '0'); |
| 576 | } else { |
| 577 | pow = pow * 10 + (c - '0'); |
| 578 | } |
| 579 | } else if (c == ':') { |
| 580 | isRoot = true; |
| 581 | } else { |
| 582 | break; |
| 583 | } |
| 584 | pos.setIndex(pos.getIndex()+1); |
| 585 | } |
| 586 | if (pow == 0) pow = 1; |
| 587 | if (root == 0) root = 1; |
| 588 | return new Exponent(isPowNegative ? -pow : pow, |
| 589 | isRootNegative ? -root : root); |
| 590 | } |
| 591 | |
| 592 | private long readLong (CharSequence csq, ParsePosition pos) { |
| 593 | final int length = csq.length(); |
| 594 | int result = 0; |
| 595 | boolean isNegative = false; |
| 596 | while (pos.getIndex() < length) { |
| 597 | char c = csq.charAt(pos.getIndex()); |
| 598 | if (c == '-') { |
| 599 | isNegative = true; |
| 600 | } else if ((c >= '0') && (c <= '9')) { |
| 601 | result = result * 10 + (c - '0'); |
| 602 | } else { |
| 603 | break; |
| 604 | } |
| 605 | pos.setIndex(pos.getIndex()+1); |
| 606 | } |
| 607 | return isNegative ? -result : result; |
| 608 | } |
| 609 | |
| 610 | private double readDouble (CharSequence csq, ParsePosition pos) { |
| 611 | final int length = csq.length(); |
| 612 | int start = pos.getIndex(); |
| 613 | int end = start+1; |
| 614 | while (end < length) { |
| 615 | if ("012356789+-.E".indexOf(csq.charAt(end)) < 0) { |
| 616 | break; |
| 617 | } |
| 618 | end += 1; |
| 619 | } |
| 620 | pos.setIndex(end+1); |
| 621 | return Double.parseDouble(csq.subSequence(start,end).toString()); |
| 622 | } |
| 623 | |
| 624 | private String readIdentifier(CharSequence csq, ParsePosition pos) { |
| 625 | final int length = csq.length(); |
| 626 | int start = pos.getIndex(); |
| 627 | int i = start; |
| 628 | while ((++i < length) && isUnitIdentifierPart(csq.charAt(i))) { } |
| 629 | pos.setIndex(i); |
| 630 | return csq.subSequence(start, i).toString(); |
| 631 | } |
| 632 | |
| 633 | //////////////////////////// |
| 634 | // Formatting. |
| 635 | |
| 636 | @Override |
| 637 | public Appendable format(Unit<?> unit, Appendable appendable) |
| 638 | throws IOException { |
| 639 | String name = nameFor(unit); |
| 640 | if (name != null) |
| 641 | return appendable.append(name); |
| 642 | if (!(unit instanceof ProductUnit)) |
| 643 | throw new IllegalArgumentException("Cannot format given Object as a Unit"); |
| 644 | |
| 645 | // Product unit. |
| 646 | ProductUnit<?> productUnit = (ProductUnit<?>) unit; |
| 647 | int invNbr = 0; |
| 648 | |
| 649 | // Write positive exponents first. |
| 650 | boolean start = true; |
| 651 | for (int i = 0; i < productUnit.getUnitCount(); i++) { |
| 652 | int pow = productUnit.getUnitPow(i); |
| 653 | if (pow >= 0) { |
| 654 | if (!start) { |
| 655 | appendable.append('·'); // Separator. |
| 656 | } |
| 657 | name = nameFor(productUnit.getUnit(i)); |
| 658 | int root = productUnit.getUnitRoot(i); |
| 659 | append(appendable, name, pow, root); |
| 660 | start = false; |
| 661 | } else { |
| 662 | invNbr++; |
| 663 | } |
| 664 | } |
| 665 | |
| 666 | // Write negative exponents. |
| 667 | if (invNbr != 0) { |
| 668 | if (start) { |
| 669 | appendable.append('1'); // e.g. 1/s |
| 670 | } |
| 671 | appendable.append('/'); |
| 672 | if (invNbr > 1) { |
| 673 | appendable.append('('); |
| 674 | } |
| 675 | start = true; |
| 676 | for (int i = 0; i < productUnit.getUnitCount(); i++) { |
| 677 | int pow = productUnit.getUnitPow(i); |
| 678 | if (pow < 0) { |
| 679 | name = nameFor(productUnit.getUnit(i)); |
| 680 | int root = productUnit.getUnitRoot(i); |
| 681 | if (!start) { |
| 682 | appendable.append('·'); // Separator. |
| 683 | } |
| 684 | append(appendable, name, -pow, root); |
| 685 | start = false; |
| 686 | } |
| 687 | } |
| 688 | if (invNbr > 1) { |
| 689 | appendable.append(')'); |
| 690 | } |
| 691 | } |
| 692 | return appendable; |
| 693 | } |
| 694 | |
| 695 | private void append(Appendable appendable, CharSequence symbol, |
| 696 | int pow, int root) throws IOException { |
| 697 | appendable.append(symbol); |
| 698 | if ((pow != 1) || (root != 1)) { |
| 699 | // Write exponent. |
| 700 | if ((pow == 2) && (root == 1)) { |
| 701 | appendable.append('²'); // Square |
| 702 | } else if ((pow == 3) && (root == 1)) { |
| 703 | appendable.append('³'); // Cubic |
| 704 | } else { |
| 705 | // Use general exponent form. |
| 706 | appendable.append('^'); |
| 707 | appendable.append(String.valueOf(pow)); |
| 708 | if (root != 1) { |
| 709 | appendable.append(':'); |
| 710 | appendable.append(String.valueOf(root)); |
| 711 | } |
| 712 | } |
| 713 | } |
| 714 | } |
| 715 | |
| 716 | private static final long serialVersionUID = 1L; |
| 717 | } |
| 718 | |
| 719 | /** |
| 720 | * This class represents the ASCIIFormat format. |
| 721 | */ |
| 722 | protected static class ASCIIFormat extends DefaultFormat { |
| 723 | |
| 724 | @Override |
| 725 | public String nameFor(Unit<?> unit) { |
| 726 | // First search if specific ASCII name should be used. |
| 727 | String name = _unitToName.get(unit); |
| 728 | if (name != null) |
| 729 | return name; |
| 730 | // Else returns default name. |
| 731 | return DEFAULT.nameFor(unit); |
| 732 | } |
| 733 | |
| 734 | @Override |
| 735 | public Unit<?> unitFor(String name) { |
| 736 | // First search if specific ASCII name. |
| 737 | Unit<?> unit = _nameToUnit.get(name); |
| 738 | if (unit != null) |
| 739 | return unit; |
| 740 | // Else returns default mapping. |
| 741 | return DEFAULT.unitFor(name); |
| 742 | } |
| 743 | |
| 744 | @Override |
| 745 | public Appendable format(Unit<?> unit, Appendable appendable) |
| 746 | throws IOException { |
| 747 | String name = nameFor(unit); |
| 748 | if (name != null) |
| 749 | return appendable.append(name); |
| 750 | if (!(unit instanceof ProductUnit)) |
| 751 | throw new IllegalArgumentException( |
| 752 | "Cannot format given Object as a Unit"); |
| 753 | |
| 754 | ProductUnit<?> productUnit = (ProductUnit<?>) unit; |
| 755 | for (int i = 0; i < productUnit.getUnitCount(); i++) { |
| 756 | if (i != 0) { |
| 757 | appendable.append('*'); // Separator. |
| 758 | } |
| 759 | name = nameFor(productUnit.getUnit(i)); |
| 760 | int pow = productUnit.getUnitPow(i); |
| 761 | int root = productUnit.getUnitRoot(i); |
| 762 | appendable.append(name); |
| 763 | if ((pow != 1) || (root != 1)) { |
| 764 | // Use general exponent form. |
| 765 | appendable.append('^'); |
| 766 | appendable.append(String.valueOf(pow)); |
| 767 | if (root != 1) { |
| 768 | appendable.append(':'); |
| 769 | appendable.append(String.valueOf(root)); |
| 770 | } |
| 771 | } |
| 772 | } |
| 773 | return appendable; |
| 774 | } |
| 775 | |
| 776 | private static final long serialVersionUID = 1L; |
| 777 | } |
| 778 | |
| 779 | |
| 780 | //////////////////////////////////////////////////////////////////////////// |
| 781 | // Initializes the standard unit database for SI units. |
| 782 | |
| 783 | private static final Unit<?>[] SI_UNITS = { SI.AMPERE, SI.BECQUEREL, |
| 784 | SI.CANDELA, SI.COULOMB, SI.FARAD, SI.GRAY, SI.HENRY, SI.HERTZ, |
| 785 | SI.JOULE, SI.KATAL, SI.KELVIN, SI.LUMEN, SI.LUX, SI.METRE, SI.MOLE, |
| 786 | SI.NEWTON, SI.OHM, SI.PASCAL, SI.RADIAN, SI.SECOND, SI.SIEMENS, |
| 787 | SI.SIEVERT, SI.STERADIAN, SI.TESLA, SI.VOLT, SI.WATT, SI.WEBER }; |
| 788 | |
| 789 | private static final String[] PREFIXES = { "Y", "Z", "E", "P", "T", "G", |
| 790 | "M", "k", "h", "da", "d", "c", "m", "µ", "n", "p", "f", "a", "z", |
| 791 | "y" }; |
| 792 | |
| 793 | private static final UnitConverter[] CONVERTERS = { E24, E21, E18, E15, E12, |
| 794 | E9, E6, E3, E2, E1, Em1, Em2, Em3, Em6, Em9, Em12, |
| 795 | Em15, Em18, Em21, Em24 }; |
| 796 | |
| 797 | private static String asciiPrefix(String prefix) { |
| 798 | return prefix == "µ" ? "micro" : prefix; |
| 799 | } |
| 800 | |
| 801 | static { |
| 802 | for (int i = 0; i < SI_UNITS.length; i++) { |
| 803 | for (int j = 0; j < PREFIXES.length; j++) { |
| 804 | Unit<?> si = SI_UNITS[i]; |
| 805 | Unit<?> u = si.transform(CONVERTERS[j]); |
| 806 | String symbol = (si instanceof BaseUnit) ? ((BaseUnit<?>) si) |
| 807 | .getSymbol() : ((AlternateUnit<?>) si).getSymbol(); |
| 808 | DEFAULT.label(u, PREFIXES[j] + symbol); |
| 809 | if (PREFIXES[j] == "µ") { |
| 810 | ASCII.label(u, "micro" + symbol); |
| 811 | } |
| 812 | } |
| 813 | } |
| 814 | // Special case for KILOGRAM. |
| 815 | DEFAULT.label(SI.GRAM, "g"); |
| 816 | for (int i = 0; i < PREFIXES.length; i++) { |
| 817 | if (CONVERTERS[i] == E3) continue; // kg is already defined. |
| 818 | DEFAULT.label(SI.KILOGRAM.transform(CONVERTERS[i].concatenate(Em3)), |
| 819 | PREFIXES[i] + "g"); |
| 820 | if (PREFIXES[i] == "µ") { |
| 821 | ASCII.label(SI.KILOGRAM.transform(CONVERTERS[i].concatenate(Em3)), "microg"); |
| 822 | } |
| 823 | } |
| 824 | |
| 825 | // Alias and ASCIIFormat for Ohm |
| 826 | DEFAULT.alias(SI.OHM, "Ohm"); |
| 827 | ASCII.label(SI.OHM, "Ohm"); |
| 828 | for (int i = 0; i < PREFIXES.length; i++) { |
| 829 | DEFAULT.alias(SI.OHM.transform(CONVERTERS[i]), PREFIXES[i] + "Ohm"); |
| 830 | ASCII.label(SI.OHM.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Ohm"); |
| 831 | } |
| 832 | |
| 833 | // Special case for DEGREE_CElSIUS. |
| 834 | DEFAULT.label(SI.CELSIUS, "℃"); |
| 835 | DEFAULT.alias(SI.CELSIUS, "°C"); |
| 836 | ASCII.label(SI.CELSIUS, "Celsius"); |
| 837 | for (int i = 0; i < PREFIXES.length; i++) { |
| 838 | DEFAULT.label(SI.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "℃"); |
| 839 | DEFAULT.alias(SI.CELSIUS.transform(CONVERTERS[i]), PREFIXES[i] + "°C"); |
| 840 | ASCII.label(SI.CELSIUS.transform(CONVERTERS[i]), asciiPrefix(PREFIXES[i]) + "Celsius"); |
| 841 | } |
| 842 | } |
| 843 | |
| 844 | //////////////////////////////////////////////////////////////////////////// |
| 845 | // To be moved in resource bundle in future release (locale dependent). |
| 846 | static { |
| 847 | DEFAULT.label(NonSI.PERCENT, "%"); |
| 848 | DEFAULT.label(NonSI.DECIBEL, "dB"); |
| 849 | DEFAULT.label(NonSI.G, "grav"); |
| 850 | DEFAULT.label(NonSI.ATOM, "atom"); |
| 851 | DEFAULT.label(NonSI.REVOLUTION, "rev"); |
| 852 | DEFAULT.label(NonSI.DEGREE_ANGLE, "°"); |
| 853 | ASCII.label(NonSI.DEGREE_ANGLE, "degree_angle"); |
| 854 | DEFAULT.label(NonSI.MINUTE_ANGLE, "'"); |
| 855 | DEFAULT.label(NonSI.SECOND_ANGLE, "\""); |
| 856 | DEFAULT.label(NonSI.CENTIRADIAN, "centiradian"); |
| 857 | DEFAULT.label(NonSI.GRADE, "grade"); |
| 858 | DEFAULT.label(NonSI.ARE, "a"); |
| 859 | DEFAULT.label(NonSI.HECTARE, "ha"); |
| 860 | DEFAULT.label(NonSI.BYTE, "byte"); |
| 861 | DEFAULT.label(NonSI.MINUTE, "min"); |
| 862 | DEFAULT.label(NonSI.HOUR, "h"); |
| 863 | DEFAULT.label(NonSI.DAY, "day"); |
| 864 | DEFAULT.label(NonSI.WEEK, "week"); |
| 865 | DEFAULT.label(NonSI.YEAR, "year"); |
| 866 | DEFAULT.label(NonSI.MONTH, "month"); |
| 867 | DEFAULT.label(NonSI.DAY_SIDEREAL, "day_sidereal"); |
| 868 | DEFAULT.label(NonSI.YEAR_SIDEREAL, "year_sidereal"); |
| 869 | DEFAULT.label(NonSI.YEAR_CALENDAR, "year_calendar"); |
| 870 | DEFAULT.label(NonSI.E, "e"); |
| 871 | DEFAULT.label(NonSI.FARADAY, "Fd"); |
| 872 | DEFAULT.label(NonSI.FRANKLIN, "Fr"); |
| 873 | DEFAULT.label(NonSI.GILBERT, "Gi"); |
| 874 | DEFAULT.label(NonSI.ERG, "erg"); |
| 875 | DEFAULT.label(NonSI.ELECTRON_VOLT, "eV"); |
| 876 | DEFAULT.label(SI.KILO(NonSI.ELECTRON_VOLT), "keV"); |
| 877 | DEFAULT.label(SI.MEGA(NonSI.ELECTRON_VOLT), "MeV"); |
| 878 | DEFAULT.label(SI.GIGA(NonSI.ELECTRON_VOLT), "GeV"); |
| 879 | DEFAULT.label(NonSI.LAMBERT, "La"); |
| 880 | DEFAULT.label(NonSI.FOOT, "ft"); |
| 881 | DEFAULT.label(NonSI.FOOT_SURVEY_US, "foot_survey_us"); |
| 882 | DEFAULT.label(NonSI.YARD, "yd"); |
| 883 | DEFAULT.label(NonSI.INCH, "in"); |
| 884 | DEFAULT.label(NonSI.MILE, "mi"); |
| 885 | DEFAULT.label(NonSI.NAUTICAL_MILE, "nmi"); |
| 886 | DEFAULT.label(NonSI.MILES_PER_HOUR, "mph"); |
| 887 | DEFAULT.label(NonSI.ANGSTROM, "Å"); |
| 888 | ASCII.label(NonSI.ANGSTROM, "Angstrom"); |
| 889 | DEFAULT.label(NonSI.ASTRONOMICAL_UNIT, "ua"); |
| 890 | DEFAULT.label(NonSI.LIGHT_YEAR, "ly"); |
| 891 | DEFAULT.label(NonSI.PARSEC, "pc"); |
| 892 | DEFAULT.label(NonSI.POINT, "pt"); |
| 893 | DEFAULT.label(NonSI.PIXEL, "pixel"); |
| 894 | DEFAULT.label(NonSI.MAXWELL, "Mx"); |
| 895 | DEFAULT.label(NonSI.GAUSS, "G"); |
| 896 | DEFAULT.label(NonSI.ATOMIC_MASS, "u"); |
| 897 | DEFAULT.label(NonSI.ELECTRON_MASS, "me"); |
| 898 | DEFAULT.label(NonSI.POUND, "lb"); |
| 899 | DEFAULT.label(NonSI.OUNCE, "oz"); |
| 900 | DEFAULT.label(NonSI.TON_US, "ton_us"); |
| 901 | DEFAULT.label(NonSI.TON_UK, "ton_uk"); |
| 902 | DEFAULT.label(NonSI.METRIC_TON, "t"); |
| 903 | DEFAULT.label(NonSI.DYNE, "dyn"); |
| 904 | DEFAULT.label(NonSI.KILOGRAM_FORCE, "kgf"); |
| 905 | DEFAULT.label(NonSI.POUND_FORCE, "lbf"); |
| 906 | DEFAULT.label(NonSI.HORSEPOWER, "hp"); |
| 907 | DEFAULT.label(NonSI.ATMOSPHERE, "atm"); |
| 908 | DEFAULT.label(NonSI.BAR, "bar"); |
| 909 | DEFAULT.label(NonSI.MILLIMETER_OF_MERCURY, "mmHg"); |
| 910 | DEFAULT.label(NonSI.INCH_OF_MERCURY, "inHg"); |
| 911 | DEFAULT.label(NonSI.RAD, "rd"); |
| 912 | DEFAULT.label(NonSI.REM, "rem"); |
| 913 | DEFAULT.label(NonSI.CURIE, "Ci"); |
| 914 | DEFAULT.label(NonSI.RUTHERFORD, "Rd"); |
| 915 | DEFAULT.label(NonSI.SPHERE, "sphere"); |
| 916 | DEFAULT.label(NonSI.RANKINE, "°R"); |
| 917 | ASCII.label(NonSI.RANKINE, "degree_rankine"); |
| 918 | DEFAULT.label(NonSI.FAHRENHEIT, "°F"); |
| 919 | ASCII.label(NonSI.FAHRENHEIT, "degree_fahrenheit"); |
| 920 | DEFAULT.label(NonSI.KNOT, "kn"); |
| 921 | DEFAULT.label(NonSI.MACH, "Mach"); |
| 922 | DEFAULT.label(NonSI.C, "c"); |
| 923 | DEFAULT.label(NonSI.LITRE, "L"); |
| 924 | DEFAULT.label(SI.MICRO(NonSI.LITRE), "µL"); |
| 925 | ASCII.label(SI.MICRO(NonSI.LITRE), "microL"); |
| 926 | DEFAULT.label(SI.MILLI(NonSI.LITRE), "mL"); |
| 927 | DEFAULT.label(SI.CENTI(NonSI.LITRE), "cL"); |
| 928 | DEFAULT.label(SI.DECI(NonSI.LITRE), "dL"); |
| 929 | DEFAULT.label(NonSI.GALLON_LIQUID_US, "gal"); |
| 930 | DEFAULT.label(NonSI.OUNCE_LIQUID_US, "oz"); |
| 931 | DEFAULT.label(NonSI.GALLON_DRY_US, "gallon_dry_us"); |
| 932 | DEFAULT.label(NonSI.GALLON_UK, "gallon_uk"); |
| 933 | DEFAULT.label(NonSI.OUNCE_LIQUID_UK, "oz_uk"); |
| 934 | DEFAULT.label(NonSI.ROENTGEN, "Roentgen"); |
| 935 | if (Locale.getDefault().getCountry().equals("GB")) { |
| 936 | DEFAULT.label(NonSI.GALLON_UK, "gal"); |
| 937 | DEFAULT.label(NonSI.OUNCE_LIQUID_UK, "oz"); |
| 938 | } |
| 939 | } |
| 940 | } |