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 | } |