001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2000-2005 Julian Hyde 008// Copyright (C) 2005-2012 Pentaho and others 009// All Rights Reserved. 010// 011// jhyde, 2 November, 2000 012*/ 013package mondrian.util; 014 015import mondrian.olap.Util; 016 017import java.io.PrintWriter; 018import java.math.BigDecimal; 019import java.math.BigInteger; 020import java.text.*; 021import java.util.*; 022 023/** 024 * <code>Format</code> formats numbers, strings and dates according to the 025 * same specification as Visual Basic's 026 * <code>format()</code> function. This function is described in more detail 027 * <a href="http://www.apostate.com/programming/vb-format.html">here</a>. We 028 * have made the following enhancements to this specification:<ul> 029 * 030 * <li>if the international currency symbol (¤) occurs in a format 031 * string, it is translated to the locale's currency symbol.</li> 032 * 033 * <li>the format string "Currency" is translated to the locale's currency 034 * format string. Negative currency values appear in parentheses.</li> 035 * 036 * <li>the string "USD" (abbreviation for U.S. Dollars) may occur in a format 037 * string.</li> 038 * 039 * </ul> 040 * 041 * <p>One format object can be used to format multiple values, thereby 042 * amortizing the time required to parse the format string. Example:</p> 043 * 044 * <pre><code> 045 * double[] values; 046 * Format format = new Format("##,##0.###;(##,##0.###);;Nil"); 047 * for (int i = 0; i < values.length; i++) { 048 * System.out.println("Value #" + i + " is " + format.format(values[i])); 049 * } 050 * </code></pre> 051 * 052 * <p>Still to be implemented:<ul> 053 * 054 * <li>String formatting (fill from left/right)</li> 055 * 056 * <li>Use client's timezone for printing times.</li> 057 * 058 * </ul> 059 * 060 * @author jhyde 061 */ 062public class Format { 063 private String formatString; 064 private BasicFormat format; 065 private FormatLocale locale; 066 067 /** 068 * Maximum number of entries in the format cache used by 069 * {@link #get(String, java.util.Locale)}. 070 */ 071 public static final int CacheLimit = 1000; 072 073 /** 074 * Maps (formatString, locale) pairs to {@link Format} objects. 075 * 076 * <p>If the number of entries in the cache exceeds 1000, 077 */ 078 private static final Map<String, Format> cache = 079 new LinkedHashMap<String, Format>() { 080 public boolean removeEldestEntry(Map.Entry<String, Format> entry) { 081 return size() > CacheLimit; 082 } 083 }; 084 085 static final char thousandSeparator_en = ','; 086 static final char decimalPlaceholder_en = '.'; 087 static final String dateSeparator_en = "/"; 088 static final String timeSeparator_en = ":"; 089 static final String currencySymbol_en = "$"; 090 static final String currencyFormat_en = "$#,##0.00"; 091 static final String[] daysOfWeekShort_en = { 092 "", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 093 }; 094 static final String[] daysOfWeekLong_en = { 095 "", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", 096 "Saturday" 097 }; 098 static final String[] monthsShort_en = { 099 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 100 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "", 101 }; 102 static final String[] monthsLong_en = { 103 "January", "February", "March", "April", "May", "June", 104 "July", "August", "September", "October", "November", "December", "", 105 }; 106 static final char intlCurrencySymbol = '\u00a4'; 107 108 /** 109 * Maps strings representing locales (for example, "en_US_Boston", "en_US", 110 * "en", or "" for the default) to a {@link Format.FormatLocale}. 111 */ 112 private static final Map<String, FormatLocale> mapLocaleToFormatLocale = 113 new HashMap<String, FormatLocale>(); 114 115 /** 116 * Cache of parsed format strings and their thousand separator 117 * tokens length. Used so we don't have to tokenize a format string 118 * over and over again. 119 */ 120 private static final Map<String, ArrayStack<Integer>> 121 thousandSeparatorTokenMap = new HashMap<String, ArrayStack<Integer>>(); 122 123 /** 124 * Locale for US English, also the default for English and for all 125 * locales. 126 */ 127 static final FormatLocale locale_US = createLocale( 128 '\0', '\0', null, null, null, null, null, null, null, null, 129 Locale.US); 130 131 static class Token { 132 final int code; 133 final int flags; 134 final String token; 135 final FormatType formatType; 136 137 Token(int code, int flags, String token) 138 { 139 this.code = code; 140 this.flags = flags; 141 this.token = token; 142 this.formatType = 143 isNumeric() ? FormatType.NUMERIC 144 : isDate() ? FormatType.DATE 145 : isString() ? FormatType.STRING 146 : null; 147 } 148 149 boolean compatibleWith(FormatType formatType) 150 { 151 return formatType == null 152 || this.formatType == null 153 || formatType == this.formatType; 154 } 155 156 boolean isSpecial() 157 { 158 return (flags & SPECIAL) == SPECIAL; 159 } 160 161 boolean isNumeric() 162 { 163 return (flags & NUMERIC) == NUMERIC; 164 } 165 166 boolean isDate() 167 { 168 return (flags & DATE) == DATE; 169 } 170 171 boolean isString() 172 { 173 return (flags & STRING) == STRING; 174 } 175 176 FormatType getFormatType() { 177 return formatType; 178 } 179 180 BasicFormat makeFormat(FormatLocale locale) 181 { 182 if (isDate()) { 183 return new DateFormat(code, token, locale, false); 184 } else if (isNumeric()) { 185 return new LiteralFormat(code, token); 186 } else { 187 return new LiteralFormat(token); 188 } 189 } 190 } 191 192 /** 193 * BasicFormat is the interface implemented by the classes which do all 194 * the work. Whereas {@link Format} has only one method for formatting, 195 * {@link Format#format(Object)}, this class provides methods for several 196 * primitive types. To make it easy to combine formatting objects, all 197 * methods write to a {@link PrintWriter}. 198 * 199 * <p>The base implementation of most of these methods throws; there 200 * is no requirement that a derived class implements all of these methods. 201 * It is up to {@link Format#parseFormatString} to ensure that, for 202 * example, the {@link #format(double,StringBuilder)} method is 203 * never called for {@link DateFormat}. 204 */ 205 static class BasicFormat { 206 final int code; 207 208 BasicFormat() { 209 this(0); 210 } 211 212 BasicFormat(int code) { 213 this.code = code; 214 } 215 216 FormatType getFormatType() { 217 return null; 218 } 219 220 void formatNull(StringBuilder buf) { 221 // SSAS formats null values as the empty string. However, SQL Server 222 // Management Studio's pivot table formats them as "(null)", so many 223 // people believe that this is the server's behavior. 224 } 225 226 void format(double d, StringBuilder buf) { 227 throw new Error(); 228 } 229 230 void format(long n, StringBuilder buf) { 231 throw new Error(); 232 } 233 234 void format(String s, StringBuilder buf) { 235 throw new Error(); 236 } 237 238 void format(Date date, StringBuilder buf) { 239 Calendar calendar = Calendar.getInstance(); // todo: use locale 240 calendar.setTime(date); 241 format(calendar, buf); 242 } 243 244 void format(Calendar calendar, StringBuilder buf) { 245 throw new Error(); 246 } 247 248 /** 249 * Returns whether this format can handle a given value. 250 * 251 * <p>Usually returns true; 252 * one notable exception is a format for negative numbers which 253 * causes the number to be underflow to zero and therefore be 254 * ineligible for the negative format. 255 * 256 * @param n value 257 * @return Whether this format is applicable for a given value 258 */ 259 boolean isApplicableTo(double n) { 260 return true; 261 } 262 263 /** 264 * Returns whether this format can handle a given value. 265 * 266 * <p>Usually returns true; 267 * one notable exception is a format for negative numbers which 268 * causes the number to be underflow to zero and therefore be 269 * ineligible for the negative format. 270 * 271 * @param n value 272 * @return Whether this format is applicable for a given value 273 */ 274 boolean isApplicableTo(long n) { 275 return true; 276 } 277 } 278 279 /** 280 * AlternateFormat is an implementation of {@link Format.BasicFormat} which 281 * allows a different format to be used for different kinds of values. If 282 * there are 4 formats, purposes are as follows:<ol> 283 * <li>positive numbers</li> 284 * <li>negative numbers</li> 285 * <li>zero</li> 286 * <li>null values</li> 287 * </ol> 288 * 289 * <p>If there are fewer than 4 formats, the first is used as a fall-back. 290 * See the <a href="http://apostate.com/vb-format-syntax">the 291 * visual basic format specification</a> for more details. 292 */ 293 static class AlternateFormat extends BasicFormat { 294 final BasicFormat[] formats; 295 final JavaFormat javaFormat; 296 297 AlternateFormat(BasicFormat[] formats, FormatLocale locale) 298 { 299 this.formats = formats; 300 assert formats.length >= 1; 301 this.javaFormat = new JavaFormat(locale.locale); 302 } 303 304 void formatNull(StringBuilder buf) { 305 if (formats.length >= 4) { 306 formats[3].format(0, buf); 307 } else { 308 super.formatNull(buf); 309 } 310 } 311 312 void format(double n, StringBuilder buf) { 313 int i; 314 if (n == 0 315 && formats.length >= 3 316 && formats[2] != null) 317 { 318 i = 2; 319 } else if (n < 0) { 320 if (formats.length >= 2 321 && formats[1] != null) 322 { 323 if (formats[1].isApplicableTo(n)) { 324 n = -n; 325 i = 1; 326 } else { 327 // Does not fit into the negative mask, so use the 328 // nil mask, if there is one. For example, 329 // "#.0;(#.0);Nil" formats -0.0001 as "Nil". 330 if (formats.length >= 3 331 && formats[2] != null) 332 { 333 i = 2; 334 } else { 335 i = 0; 336 } 337 } 338 } else { 339 i = 0; 340 if (formats[0].isApplicableTo(n)) { 341 if (!Bug.BugMondrian687Fixed) { 342 // Special case for format strings with style, 343 // like "|#|style='red'". JPivot expects the 344 // '-' to immediately precede the digits, viz 345 // "|-6|style='red'|", not "-|6|style='red'|". 346 // This is not consistent with SSAS 2005, hence 347 // the bug. 348 // 349 // But for other formats, we want '-' to precede 350 // literals, viz '-$6' not '$-6'. This is SSAS 351 // 2005's behavior too. 352 int size = buf.length(); 353 buf.append('-'); 354 n = -n; 355 formats[i].format(n, buf); 356 if (buf.substring(size, size + 2).equals( 357 "-|")) 358 { 359 buf.setCharAt(size, '|'); 360 buf.setCharAt(size + 1, '-'); 361 } 362 return; 363 } 364 buf.append('-'); 365 n = -n; 366 } else { 367 n = 0; 368 } 369 } 370 } else { 371 i = 0; 372 } 373 formats[i].format(n, buf); 374 } 375 376 void format(long n, StringBuilder buf) { 377 int i; 378 if (n == 0 379 && formats.length >= 3 380 && formats[2] != null) 381 { 382 i = 2; 383 } else if (n < 0) { 384 if (formats.length >= 2 385 && formats[1] != null) 386 { 387 if (formats[1].isApplicableTo(n)) { 388 n = -n; 389 i = 1; 390 } else { 391 // Does not fit into the negative mask, so use the 392 // nil mask, if there is one. For example, 393 // "#.0;(#.0);Nil" formats -0.0001 as "Nil". 394 if (formats.length >= 3 395 && formats[2] != null) 396 { 397 i = 2; 398 } else { 399 i = 0; 400 } 401 } 402 } else { 403 i = 0; 404 if (formats[0].isApplicableTo(n)) { 405 if (!Bug.BugMondrian687Fixed) { 406 // Special case for format strings with style, 407 // like "|#|style='red'". JPivot expects the 408 // '-' to immediately precede the digits, viz 409 // "|-6|style='red'|", not "-|6|style='red'|". 410 // This is not consistent with SSAS 2005, hence 411 // the bug. 412 // 413 // But for other formats, we want '-' to precede 414 // literals, viz '-$6' not '$-6'. This is SSAS 415 // 2005's behavior too. 416 final int size = buf.length(); 417 buf.append('-'); 418 n = -n; 419 formats[i].format(n, buf); 420 if (buf.substring(size, size + 2).equals( 421 "-|")) 422 { 423 buf.setCharAt(size, '|'); 424 buf.setCharAt(size + 1, '-'); 425 } 426 return; 427 } 428 buf.append('-'); 429 n = -n; 430 } else { 431 n = 0; 432 } 433 } 434 } else { 435 i = 0; 436 } 437 formats[i].format(n, buf); 438 } 439 440 void format(String s, StringBuilder buf) { 441 // since it is not a number, ignore all format strings 442 buf.append(s); 443 } 444 445 void format(Calendar calendar, StringBuilder buf) { 446 // We're passing a date to a numeric format string. Convert it to 447 // the number of days since 1900. 448 BigDecimal bigDecimal = daysSince1900(calendar); 449 450 // since it is not a number, ignore all format strings 451 format(bigDecimal.doubleValue(), buf); 452 } 453 454 private static BigDecimal daysSince1900(Calendar calendar) { 455 final long dayOfYear = calendar.get(Calendar.DAY_OF_YEAR); 456 final long year = calendar.get(Calendar.YEAR); 457 long yearForLeap = year; 458 if (calendar.get(Calendar.MONTH) < 2) { 459 --yearForLeap; 460 } 461 final long leapDays = 462 (yearForLeap - 1900) / 4 463 - (yearForLeap - 1900) / 100 464 + (yearForLeap - 2000) / 400; 465 final long days = 466 (year - 1900) * 365 467 + leapDays 468 + dayOfYear 469 + 2; // kludge factor to agree with Excel 470 final long millis = 471 calendar.get(Calendar.HOUR_OF_DAY) * 3600000 472 + calendar.get(Calendar.MINUTE) * 60000 473 + calendar.get(Calendar.SECOND) * 1000 474 + calendar.get(Calendar.MILLISECOND); 475 return BigDecimal.valueOf(days).add( 476 BigDecimal.valueOf(millis).divide( 477 BigDecimal.valueOf(86400000), 478 8, 479 BigDecimal.ROUND_FLOOR)); 480 } 481 } 482 483 /** 484 * LiteralFormat is an implementation of {@link Format.BasicFormat} which 485 * prints a constant value, regardless of the value to be formatted. 486 * 487 * @see CompoundFormat 488 */ 489 static class LiteralFormat extends BasicFormat 490 { 491 String s; 492 493 LiteralFormat(String s) 494 { 495 this(FORMAT_LITERAL, s); 496 } 497 498 LiteralFormat(int code, String s) 499 { 500 super(code); 501 this.s = s; 502 } 503 504 void format(double d, StringBuilder buf) { 505 buf.append(s); 506 } 507 508 void format(long n, StringBuilder buf) { 509 buf.append(s); 510 } 511 512 void format(String str, StringBuilder buf) { 513 buf.append(s); 514 } 515 516 void format(Date date, StringBuilder buf) { 517 buf.append(s); 518 } 519 520 void format(Calendar calendar, StringBuilder buf) { 521 buf.append(s); 522 } 523 } 524 525 /** 526 * CompoundFormat is an implementation of {@link Format.BasicFormat} where 527 * each value is formatted by applying a sequence of format elements. Each 528 * format element is itself a format. 529 * 530 * @see AlternateFormat 531 */ 532 static class CompoundFormat extends BasicFormat 533 { 534 final BasicFormat[] formats; 535 CompoundFormat(BasicFormat[] formats) 536 { 537 this.formats = formats; 538 assert formats.length >= 2; 539 } 540 541 void format(double v, StringBuilder buf) { 542 for (int i = 0; i < formats.length; i++) { 543 formats[i].format(v, buf); 544 } 545 } 546 547 void format(long v, StringBuilder buf) { 548 for (int i = 0; i < formats.length; i++) { 549 formats[i].format(v, buf); 550 } 551 } 552 553 void format(String v, StringBuilder buf) { 554 for (int i = 0; i < formats.length; i++) { 555 formats[i].format(v, buf); 556 } 557 } 558 559 void format(Date v, StringBuilder buf) { 560 for (int i = 0; i < formats.length; i++) { 561 formats[i].format(v, buf); 562 } 563 } 564 565 void format(Calendar v, StringBuilder buf) { 566 for (int i = 0; i < formats.length; i++) { 567 formats[i].format(v, buf); 568 } 569 } 570 571 boolean isApplicableTo(double n) { 572 for (int i = 0; i < formats.length; i++) { 573 if (!formats[i].isApplicableTo(n)) { 574 return false; 575 } 576 } 577 return true; 578 } 579 } 580 581 /** 582 * JavaFormat is an implementation of {@link Format.BasicFormat} which 583 * prints values using Java's default formatting for their type. 584 * <code>null</code> values appear as an empty string. 585 */ 586 static class JavaFormat extends BasicFormat 587 { 588 private final NumberFormat numberFormat; 589 private final java.text.DateFormat dateFormat; 590 591 JavaFormat(Locale locale) 592 { 593 this.numberFormat = NumberFormat.getNumberInstance(locale); 594 this.dateFormat = 595 java.text.DateFormat.getDateTimeInstance( 596 java.text.DateFormat.SHORT, 597 java.text.DateFormat.MEDIUM, 598 locale); 599 } 600 601 // No need to override format(Object,PrintWriter) or 602 // format(Date,PrintWriter). 603 604 void format(double d, StringBuilder buf) { 605 // NOTE (jhyde, 2006/12/1): We'd use 606 // NumberFormat(double,StringBuilder,FieldPosition) if it existed. 607 buf.append(numberFormat.format(d)); 608 } 609 610 void format(long n, StringBuilder buf) { 611 // NOTE (jhyde, 2006/12/1): We'd use 612 // NumberFormat(long,StringBuilder,FieldPosition) if it existed. 613 buf.append(numberFormat.format(n)); 614 } 615 616 void format(String s, StringBuilder buf) { 617 buf.append(s); 618 } 619 620 void format(Calendar calendar, StringBuilder buf) { 621 // NOTE (jhyde, 2006/12/1): We'd use 622 // NumberFormat(Date,StringBuilder,FieldPosition) if it existed. 623 buf.append(dateFormat.format(calendar.getTime())); 624 } 625 } 626 627 /** 628 * FallbackFormat catches un-handled datatypes and prints the original 629 * format string. Better than giving an error. Abstract base class for 630 * NumericFormat and DateFormat. 631 */ 632 static abstract class FallbackFormat extends BasicFormat 633 { 634 final String token; 635 636 FallbackFormat(int code, String token) 637 { 638 super(code); 639 this.token = token; 640 } 641 642 void format(double d, StringBuilder buf) { 643 buf.append(token); 644 } 645 646 void format(long n, StringBuilder buf) { 647 buf.append(token); 648 } 649 650 void format(String s, StringBuilder buf) { 651 buf.append(token); 652 } 653 654 void format(Calendar calendar, StringBuilder buf) { 655 buf.append(token); 656 } 657 } 658 659 /** 660 * NumericFormat is an implementation of {@link Format.BasicFormat} which 661 * prints numbers with a given number of decimal places, leading zeroes, in 662 * exponential notation, etc. 663 * 664 * <p>It is implemented using {@link MondrianFloatingDecimal}. 665 */ 666 static class NumericFormat extends JavaFormat 667 { 668 final FormatLocale locale; 669 final int digitsLeftOfPoint; 670 final int zeroesLeftOfPoint; 671 final int digitsRightOfPoint; 672 final int zeroesRightOfPoint; 673 final int digitsRightOfExp; 674 final int zeroesRightOfExp; 675 676 /** 677 * Number of decimal places to shift the number left before 678 * formatting it: 2 means multiply by 100; -3 means divide by 679 * 1000. 680 */ 681 int decimalShift; 682 final char expChar; 683 final boolean expSign; 684 final boolean useDecimal; 685 final boolean useThouSep; 686 687 final ArrayStack<Integer> cachedThousandSeparatorPositions; 688 689 NumericFormat( 690 String token, 691 FormatLocale locale, 692 int expFormat, 693 int digitsLeftOfPoint, int zeroesLeftOfPoint, 694 int digitsRightOfPoint, int zeroesRightOfPoint, 695 int digitsRightOfExp, int zeroesRightOfExp, 696 boolean useDecimal, boolean useThouSep, 697 String formatString) 698 { 699 super(locale.locale); 700 this.locale = locale; 701 switch (expFormat) { 702 case FORMAT_E_MINUS_UPPER: 703 this.expChar = 'E'; 704 this.expSign = false; 705 break; 706 case FORMAT_E_PLUS_UPPER: 707 this.expChar = 'E'; 708 this.expSign = true; 709 break; 710 case FORMAT_E_MINUS_LOWER: 711 this.expChar = 'e'; 712 this.expSign = false; 713 break; 714 case FORMAT_E_PLUS_LOWER: 715 this.expChar = 'e'; 716 this.expSign = true; 717 break; 718 default: 719 this.expChar = 0; 720 this.expSign = false; 721 } 722 this.digitsLeftOfPoint = digitsLeftOfPoint; 723 this.zeroesLeftOfPoint = zeroesLeftOfPoint; 724 this.digitsRightOfPoint = digitsRightOfPoint; 725 this.zeroesRightOfPoint = zeroesRightOfPoint; 726 this.digitsRightOfExp = digitsRightOfExp; 727 this.zeroesRightOfExp = zeroesRightOfExp; 728 this.useDecimal = useDecimal; 729 this.useThouSep = useThouSep; 730 this.decimalShift = 0; // set later 731 732 // Check if we're dealing with a format macro token rather than 733 // an actual format string. 734 formatString = MacroToken.expand(locale, formatString); 735 736 if (thousandSeparatorTokenMap.containsKey(formatString)) { 737 cachedThousandSeparatorPositions = 738 thousandSeparatorTokenMap.get(formatString); 739 } else { 740 // To provide backwards compatibility, we apply the old 741 // formatting rules if there are less than 2 thousand 742 // separators in the format string. 743 String formatStringBuffer = formatString; 744 745 // If the format includes a negative format part, we strip it. 746 final int semiPos = 747 formatStringBuffer.indexOf(getFormatToken(FORMAT_SEMI)); 748 if (semiPos > 0) { 749 formatStringBuffer = 750 formatStringBuffer.substring(0, semiPos); 751 } 752 753 final int nbThousandSeparators = 754 countOccurrences( 755 formatStringBuffer, 756 getFormatToken(FORMAT_THOUSEP).charAt(0)); 757 cachedThousandSeparatorPositions = new ArrayStack<Integer>(); 758 if (nbThousandSeparators > 1) { 759 // Extract the whole part of the format string 760 final int decimalPos = 761 formatStringBuffer.indexOf( 762 getFormatToken(FORMAT_DECIMAL)); 763 final int endIndex = 764 decimalPos == -1 765 ? formatStringBuffer.length() 766 : decimalPos; 767 final String wholeFormat = 768 formatStringBuffer.substring(0, endIndex); 769 770 // Tokenize it so we can analyze it's structure 771 final StringTokenizer st = 772 new StringTokenizer( 773 wholeFormat, 774 String.valueOf( 775 getFormatToken(FORMAT_THOUSEP))); 776 777 // We ignore the first token. 778 // ie: #,###,### 779 st.nextToken(); 780 781 // Now we build a list of the token lengths in 782 // reverse order. The last one in the reversed 783 // list will be re-applied if the number is 784 // longer than the format string. 785 while (st.hasMoreTokens()) { 786 cachedThousandSeparatorPositions.push( 787 st.nextToken().length()); 788 } 789 } else if (nbThousandSeparators == 1) { 790 // Use old style formatting. 791 cachedThousandSeparatorPositions.add(3); 792 } 793 thousandSeparatorTokenMap.put( 794 formatString, cachedThousandSeparatorPositions); 795 } 796 } 797 798 FormatType getFormatType() { 799 return FormatType.NUMERIC; 800 } 801 802 private ArrayStack<Integer> getThousandSeparatorPositions() { 803 // Defensive copy 804 return new ArrayStack<Integer>(cachedThousandSeparatorPositions); 805 } 806 807 private int countOccurrences(final String s, final char c) { 808 final char[] chars = s.toCharArray(); 809 int count = 0; 810 for (int i = 0; i < chars.length; i++) { 811 if (chars[i] == c) { 812 count++; 813 } 814 } 815 return count; 816 } 817 818 void format(double n, StringBuilder buf) 819 { 820 MondrianFloatingDecimal fd = new MondrianFloatingDecimal(n); 821 shift(fd, decimalShift); 822 final int formatDigitsRightOfPoint = 823 zeroesRightOfPoint + digitsRightOfPoint; 824 if (n == 0.0 || (n < 0 && !shows(fd, formatDigitsRightOfPoint))) { 825 // Underflow of negative number. Make it zero, so there is no 826 // '-' sign. 827 fd = new MondrianFloatingDecimal(0); 828 } 829 formatFd0( 830 fd, 831 buf, 832 zeroesLeftOfPoint, 833 locale.decimalPlaceholder, 834 zeroesRightOfPoint, 835 formatDigitsRightOfPoint, 836 expChar, 837 expSign, 838 zeroesRightOfExp, 839 useThouSep ? locale.thousandSeparator : '\0', 840 useDecimal, 841 getThousandSeparatorPositions()); 842 } 843 844 boolean isApplicableTo(double n) { 845 if (n >= 0) { 846 return true; 847 } 848 MondrianFloatingDecimal fd = new MondrianFloatingDecimal(n); 849 shift(fd, decimalShift); 850 final int formatDigitsRightOfPoint = 851 zeroesRightOfPoint + digitsRightOfPoint; 852 return shows(fd, formatDigitsRightOfPoint); 853 } 854 855 private static boolean shows( 856 MondrianFloatingDecimal fd, 857 int formatDigitsRightOfPoint) 858 { 859 final int i0 = - fd.decExponent - formatDigitsRightOfPoint; 860 if (i0 < 0) { 861 return true; 862 } 863 if (i0 > 0) { 864 return false; 865 } 866 if (fd.digits[0] >= '5') { 867 return true; 868 } 869 return false; 870 } 871 872 void format(long n, StringBuilder buf) 873 { 874 MondrianFloatingDecimal fd = 875 new MondrianFloatingDecimal(n); 876 shift(fd, decimalShift); 877 formatFd0( 878 fd, 879 buf, 880 zeroesLeftOfPoint, 881 locale.decimalPlaceholder, 882 zeroesRightOfPoint, 883 zeroesRightOfPoint + digitsRightOfPoint, 884 expChar, 885 expSign, 886 zeroesRightOfExp, 887 useThouSep ? locale.thousandSeparator : '\0', 888 useDecimal, 889 getThousandSeparatorPositions()); 890 } 891 } 892 893 /** 894 * DateFormat is an element of a {@link Format.CompoundFormat} which has a 895 * value when applied to a {@link Calendar} object. (Values of type {@link 896 * Date} are automatically converted into {@link Calendar}s when you call 897 * {@link Format.BasicFormat#format(Date, StringBuilder)} calls 898 * to format other kinds of values give a runtime error.) 899 * 900 * <p>In a typical use of this class, a format string such as "m/d/yy" is 901 * parsed into DateFormat objects for "m", "d", and "yy", and {@link 902 * LiteralFormat} objects for "/". A {@link Format.CompoundFormat} object 903 * is created to bind them together. 904 */ 905 static class DateFormat extends FallbackFormat 906 { 907 FormatLocale locale; 908 boolean twelveHourClock; 909 910 DateFormat( 911 int code, String s, FormatLocale locale, boolean twelveHourClock) 912 { 913 super(code, s); 914 this.locale = locale; 915 this.twelveHourClock = twelveHourClock; 916 } 917 918 FormatType getFormatType() { 919 return FormatType.DATE; 920 } 921 922 void setTwelveHourClock(boolean twelveHourClock) 923 { 924 this.twelveHourClock = twelveHourClock; 925 } 926 927 void format(Calendar calendar, StringBuilder buf) 928 { 929 format(code, calendar, buf); 930 } 931 932 private void format( 933 int code, 934 Calendar calendar, 935 StringBuilder buf) 936 { 937 switch (code) { 938 case FORMAT_C: 939 { 940 boolean dateSet = !( 941 calendar.get(Calendar.DAY_OF_YEAR) == 0 942 && calendar.get(Calendar.YEAR) == 0); 943 boolean timeSet = !( 944 calendar.get(Calendar.SECOND) == 0 945 && calendar.get(Calendar.MINUTE) == 0 946 && calendar.get(Calendar.HOUR) == 0); 947 if (dateSet) { 948 format(FORMAT_DDDDD, calendar, buf); 949 } 950 if (dateSet && timeSet) { 951 buf.append(' '); 952 } 953 if (timeSet) { 954 format(FORMAT_TTTTT, calendar, buf); 955 } 956 break; 957 } 958 case FORMAT_D: 959 { 960 int d = calendar.get(Calendar.DAY_OF_MONTH); 961 buf.append(d); 962 break; 963 } 964 case FORMAT_DD: 965 { 966 int d = calendar.get(Calendar.DAY_OF_MONTH); 967 if (d < 10) { 968 buf.append('0'); 969 } 970 buf.append(d); 971 break; 972 } 973 case FORMAT_DDD: 974 { 975 int dow = calendar.get(Calendar.DAY_OF_WEEK); 976 buf.append(locale.daysOfWeekShort[dow]); // e.g. Sun 977 break; 978 } 979 case FORMAT_DDDD: 980 { 981 int dow = calendar.get(Calendar.DAY_OF_WEEK); 982 buf.append(locale.daysOfWeekLong[dow]); // e.g. Sunday 983 break; 984 } 985 case FORMAT_DDDDD: 986 { 987 // Officially, we should use the system's short date 988 // format. But for now, we always print using the default 989 // format, m/d/yy. 990 format(FORMAT_M, calendar, buf); 991 buf.append(locale.dateSeparator); 992 format(FORMAT_D, calendar, buf); 993 buf.append(locale.dateSeparator); 994 format(FORMAT_YY, calendar, buf); 995 break; 996 } 997 case FORMAT_DDDDDD: 998 { 999 format(FORMAT_MMMM_UPPER, calendar, buf); 1000 buf.append(" "); 1001 format(FORMAT_DD, calendar, buf); 1002 buf.append(", "); 1003 format(FORMAT_YYYY, calendar, buf); 1004 break; 1005 } 1006 case FORMAT_W: 1007 { 1008 int dow = calendar.get(Calendar.DAY_OF_WEEK); 1009 buf.append(dow); 1010 break; 1011 } 1012 case FORMAT_WW: 1013 { 1014 int woy = calendar.get(Calendar.WEEK_OF_YEAR); 1015 buf.append(woy); 1016 break; 1017 } 1018 case FORMAT_M: 1019 { 1020 int m = calendar.get(Calendar.MONTH) + 1; // 0-based 1021 buf.append(m); 1022 break; 1023 } 1024 case FORMAT_MM: 1025 { 1026 int mm = calendar.get(Calendar.MONTH) + 1; // 0-based 1027 if (mm < 10) { 1028 buf.append('0'); 1029 } 1030 buf.append(mm); 1031 break; 1032 } 1033 case FORMAT_MMM_LOWER: 1034 case FORMAT_MMM_UPPER: 1035 { 1036 int m = calendar.get(Calendar.MONTH); // 0-based 1037 buf.append(locale.monthsShort[m]); // e.g. Jan 1038 break; 1039 } 1040 case FORMAT_MMMM_LOWER: 1041 case FORMAT_MMMM_UPPER: 1042 { 1043 int m = calendar.get(Calendar.MONTH); // 0-based 1044 buf.append(locale.monthsLong[m]); // e.g. January 1045 break; 1046 } 1047 case FORMAT_Q: 1048 { 1049 int m = calendar.get(Calendar.MONTH); 1050 // 0(Jan) -> q1, 1(Feb) -> q1, 2(Mar) -> q1, 3(Apr) -> q2 1051 int q = m / 3 + 1; 1052 buf.append(q); 1053 break; 1054 } 1055 case FORMAT_Y: 1056 { 1057 int doy = calendar.get(Calendar.DAY_OF_YEAR); 1058 buf.append(doy); 1059 break; 1060 } 1061 case FORMAT_YY: 1062 { 1063 int y = calendar.get(Calendar.YEAR) % 100; 1064 if (y < 10) { 1065 buf.append('0'); 1066 } 1067 buf.append(y); 1068 break; 1069 } 1070 case FORMAT_YYYY: 1071 { 1072 int y = calendar.get(Calendar.YEAR); 1073 buf.append(y); 1074 break; 1075 } 1076 case FORMAT_H: 1077 { 1078 int h = calendar.get( 1079 twelveHourClock ? Calendar.HOUR : Calendar.HOUR_OF_DAY); 1080 buf.append(h); 1081 break; 1082 } 1083 case FORMAT_HH: 1084 { 1085 int h = calendar.get( 1086 twelveHourClock ? Calendar.HOUR : Calendar.HOUR_OF_DAY); 1087 if (h < 10) { 1088 buf.append('0'); 1089 } 1090 buf.append(h); 1091 break; 1092 } 1093 case FORMAT_N: 1094 { 1095 int n = calendar.get(Calendar.MINUTE); 1096 buf.append(n); 1097 break; 1098 } 1099 case FORMAT_NN: 1100 { 1101 int n = calendar.get(Calendar.MINUTE); 1102 if (n < 10) { 1103 buf.append('0'); 1104 } 1105 buf.append(n); 1106 break; 1107 } 1108 case FORMAT_S: 1109 { 1110 int s = calendar.get(Calendar.SECOND); 1111 buf.append(s); 1112 break; 1113 } 1114 case FORMAT_SS: 1115 { 1116 int s = calendar.get(Calendar.SECOND); 1117 if (s < 10) { 1118 buf.append('0'); 1119 } 1120 buf.append(s); 1121 break; 1122 } 1123 case FORMAT_TTTTT: 1124 { 1125 // Officially, we should use the system's time format. But 1126 // for now, we always print using the default format, h:mm:ss. 1127 format(FORMAT_H, calendar, buf); 1128 buf.append(locale.timeSeparator); 1129 format(FORMAT_NN, calendar, buf); 1130 buf.append(locale.timeSeparator); 1131 format(FORMAT_SS, calendar, buf); 1132 break; 1133 } 1134 case FORMAT_AMPM: 1135 case FORMAT_UPPER_AM_SOLIDUS_PM: 1136 { 1137 boolean isAm = calendar.get(Calendar.AM_PM) == Calendar.AM; 1138 buf.append(isAm ? "AM" : "PM"); 1139 break; 1140 } 1141 case FORMAT_LOWER_AM_SOLIDUS_PM: 1142 { 1143 boolean isAm = calendar.get(Calendar.AM_PM) == Calendar.AM; 1144 buf.append(isAm ? "am" : "pm"); 1145 break; 1146 } 1147 case FORMAT_UPPER_A_SOLIDUS_P: 1148 { 1149 boolean isAm = calendar.get(Calendar.AM_PM) == Calendar.AM; 1150 buf.append(isAm ? "A" : "P"); 1151 break; 1152 } 1153 case FORMAT_LOWER_A_SOLIDUS_P: 1154 { 1155 boolean isAm = calendar.get(Calendar.AM_PM) == Calendar.AM; 1156 buf.append(isAm ? "a" : "p"); 1157 break; 1158 } 1159 default: 1160 throw new Error(); 1161 } 1162 } 1163 } 1164 1165 /** 1166 * A FormatLocale contains all information necessary to format objects 1167 * based upon the locale of the end-user. Use {@link Format#createLocale} 1168 * to make one. 1169 */ 1170 public static class FormatLocale 1171 { 1172 char thousandSeparator; 1173 char decimalPlaceholder; 1174 String dateSeparator; 1175 String timeSeparator; 1176 String currencySymbol; 1177 String currencyFormat; 1178 String[] daysOfWeekShort; 1179 String[] daysOfWeekLong; 1180 String[] monthsShort; 1181 String[] monthsLong; 1182 private final Locale locale; 1183 1184 private FormatLocale( 1185 char thousandSeparator, 1186 char decimalPlaceholder, 1187 String dateSeparator, 1188 String timeSeparator, 1189 String currencySymbol, 1190 String currencyFormat, 1191 String[] daysOfWeekShort, 1192 String[] daysOfWeekLong, 1193 String[] monthsShort, 1194 String[] monthsLong, 1195 Locale locale) 1196 { 1197 this.locale = locale; 1198 if (thousandSeparator == '\0') { 1199 thousandSeparator = thousandSeparator_en; 1200 } 1201 this.thousandSeparator = thousandSeparator; 1202 if (decimalPlaceholder == '\0') { 1203 decimalPlaceholder = decimalPlaceholder_en; 1204 } 1205 this.decimalPlaceholder = decimalPlaceholder; 1206 if (dateSeparator == null) { 1207 dateSeparator = dateSeparator_en; 1208 } 1209 this.dateSeparator = dateSeparator; 1210 if (timeSeparator == null) { 1211 timeSeparator = timeSeparator_en; 1212 } 1213 this.timeSeparator = timeSeparator; 1214 if (currencySymbol == null) { 1215 currencySymbol = currencySymbol_en; 1216 } 1217 this.currencySymbol = currencySymbol; 1218 if (currencyFormat == null) { 1219 currencyFormat = currencyFormat_en; 1220 } 1221 this.currencyFormat = currencyFormat; 1222 if (daysOfWeekShort == null) { 1223 daysOfWeekShort = daysOfWeekShort_en; 1224 } 1225 this.daysOfWeekShort = daysOfWeekShort; 1226 if (daysOfWeekLong == null) { 1227 daysOfWeekLong = daysOfWeekLong_en; 1228 } 1229 this.daysOfWeekLong = daysOfWeekLong; 1230 if (monthsShort == null) { 1231 monthsShort = monthsShort_en; 1232 } 1233 this.monthsShort = monthsShort; 1234 if (monthsLong == null) { 1235 monthsLong = monthsLong_en; 1236 } 1237 this.monthsLong = monthsLong; 1238 if (daysOfWeekShort.length != 8 1239 || daysOfWeekLong.length != 8 1240 || monthsShort.length != 13 1241 || monthsLong.length != 13) 1242 { 1243 throw new IllegalArgumentException( 1244 "Format: day or month array has incorrect length"); 1245 } 1246 } 1247 } 1248 1249 private static class StringFormat extends BasicFormat 1250 { 1251 final StringCase stringCase; 1252 final String literal; 1253 final JavaFormat javaFormat; 1254 1255 StringFormat(StringCase stringCase, String literal, Locale locale) { 1256 assert stringCase != null; 1257 this.stringCase = stringCase; 1258 this.literal = literal; 1259 this.javaFormat = new JavaFormat(locale); 1260 } 1261 1262 @Override 1263 void format(String s, StringBuilder buf) { 1264 switch (stringCase) { 1265 case UPPER: 1266 s = s.toUpperCase(); 1267 break; 1268 case LOWER: 1269 s = s.toLowerCase(); 1270 break; 1271 } 1272 buf.append(s); 1273 } 1274 1275 void format(double d, StringBuilder buf) { 1276 final int x = buf.length(); 1277 javaFormat.format(d, buf); 1278 String s = buf.substring(x); 1279 buf.setLength(x); 1280 format(s, buf); 1281 } 1282 1283 void format(long n, StringBuilder buf) { 1284 final int x = buf.length(); 1285 javaFormat.format(n, buf); 1286 String s = buf.substring(x); 1287 buf.setLength(x); 1288 format(s, buf); 1289 } 1290 1291 void format(Date date, StringBuilder buf) { 1292 final int x = buf.length(); 1293 javaFormat.format(date, buf); 1294 String s = buf.substring(x); 1295 buf.setLength(x); 1296 format(s, buf); 1297 } 1298 1299 void format(Calendar calendar, StringBuilder buf) { 1300 final int x = buf.length(); 1301 javaFormat.format(calendar, buf); 1302 String s = buf.substring(x); 1303 buf.setLength(x); 1304 format(s, buf); 1305 } 1306 } 1307 1308 private enum StringCase { 1309 UPPER, 1310 LOWER 1311 } 1312 1313 /** Types of Format. */ 1314 private static final int GENERAL = 0; 1315 private static final int DATE = 1; 1316 private static final int NUMERIC = 2; 1317 private static final int STRING = 4; 1318 1319 /** A Format is flagged SPECIAL if it needs special processing 1320 * during parsing. */ 1321 private static final int SPECIAL = 8; 1322 1323 /** Values for {@link Format.BasicFormat#code}. */ 1324 private static final int FORMAT_NULL = 0; 1325 private static final int FORMAT_C = 3; 1326 private static final int FORMAT_D = 4; 1327 private static final int FORMAT_DD = 5; 1328 private static final int FORMAT_DDD = 6; 1329 private static final int FORMAT_DDDD = 7; 1330 private static final int FORMAT_DDDDD = 8; 1331 private static final int FORMAT_DDDDDD = 9; 1332 private static final int FORMAT_W = 10; 1333 private static final int FORMAT_WW = 11; 1334 private static final int FORMAT_M = 12; 1335 private static final int FORMAT_MM = 13; 1336 private static final int FORMAT_MMM_UPPER = 14; 1337 private static final int FORMAT_MMMM_UPPER = 15; 1338 private static final int FORMAT_Q = 16; 1339 private static final int FORMAT_Y = 17; 1340 private static final int FORMAT_YY = 18; 1341 private static final int FORMAT_YYYY = 19; 1342 private static final int FORMAT_H = 20; 1343 private static final int FORMAT_HH = 21; 1344 private static final int FORMAT_N = 22; 1345 private static final int FORMAT_NN = 23; 1346 private static final int FORMAT_S = 24; 1347 private static final int FORMAT_SS = 25; 1348 private static final int FORMAT_TTTTT = 26; 1349 private static final int FORMAT_UPPER_AM_SOLIDUS_PM = 27; 1350 private static final int FORMAT_LOWER_AM_SOLIDUS_PM = 28; 1351 private static final int FORMAT_UPPER_A_SOLIDUS_P = 29; 1352 private static final int FORMAT_LOWER_A_SOLIDUS_P = 30; 1353 private static final int FORMAT_AMPM = 31; 1354 private static final int FORMAT_0 = 32; 1355 private static final int FORMAT_POUND = 33; 1356 private static final int FORMAT_DECIMAL = 34; 1357 private static final int FORMAT_PERCENT = 35; 1358 private static final int FORMAT_THOUSEP = 36; 1359 private static final int FORMAT_TIMESEP = 37; 1360 private static final int FORMAT_DATESEP = 38; 1361 private static final int FORMAT_E_MINUS_UPPER = 39; 1362 private static final int FORMAT_E_PLUS_UPPER = 40; 1363 private static final int FORMAT_E_MINUS_LOWER = 41; 1364 private static final int FORMAT_E_PLUS_LOWER = 42; 1365 private static final int FORMAT_LITERAL = 43; 1366 private static final int FORMAT_BACKSLASH = 44; 1367 private static final int FORMAT_QUOTE = 45; 1368 private static final int FORMAT_CHARACTER_OR_SPACE = 46; 1369 private static final int FORMAT_CHARACTER_OR_NOTHING = 47; 1370 private static final int FORMAT_LOWER = 48; 1371 private static final int FORMAT_UPPER = 49; 1372 private static final int FORMAT_FILL_FROM_LEFT = 50; 1373 private static final int FORMAT_SEMI = 51; 1374 private static final int FORMAT_GENERAL_NUMBER = 52; 1375 private static final int FORMAT_GENERAL_DATE = 53; 1376 private static final int FORMAT_INTL_CURRENCY = 54; 1377 private static final int FORMAT_MMM_LOWER = 55; 1378 private static final int FORMAT_MMMM_LOWER = 56; 1379 private static final int FORMAT_USD = 57; 1380 1381 private static final Map<Integer, String> formatTokenToFormatString = 1382 new HashMap<Integer, String>(); 1383 1384 private static Token nfe( 1385 int code, int flags, String token, String purpose, String description) 1386 { 1387 Util.discard(purpose); 1388 Util.discard(description); 1389 formatTokenToFormatString.put(code, token); 1390 return new Token(code, flags, token); 1391 } 1392 1393 public static List<Token> getTokenList() 1394 { 1395 return Collections.unmodifiableList(Arrays.asList(tokens)); 1396 } 1397 1398 /** 1399 * Returns the format token as a string representation 1400 * which corresponds to a given token code. 1401 * @param code The code of the token to obtain. 1402 * @return The string representation of that token. 1403 */ 1404 public static String getFormatToken(int code) 1405 { 1406 return formatTokenToFormatString.get(code); 1407 } 1408 1409 private static final Token[] tokens = { 1410 nfe( 1411 FORMAT_NULL, 1412 NUMERIC, 1413 null, 1414 "No formatting", 1415 "Display the number with no formatting."), 1416 nfe( 1417 FORMAT_C, 1418 DATE, 1419 "C", 1420 null, 1421 "Display the date as ddddd and display the time as t t t t t, in " 1422 + "that order. Display only date information if there is no " 1423 + "fractional part to the date serial number; display only time " 1424 + "information if there is no integer portion."), 1425 nfe( 1426 FORMAT_D, 1427 DATE, 1428 "d", 1429 null, 1430 "Display the day as a number without a leading zero (1 - 31)."), 1431 nfe( 1432 FORMAT_DD, 1433 DATE, 1434 "dd", 1435 null, 1436 "Display the day as a number with a leading zero (01 - 31)."), 1437 nfe( 1438 FORMAT_DDD, 1439 DATE, 1440 "Ddd", 1441 null, 1442 "Display the day as an abbreviation (Sun - Sat)."), 1443 nfe( 1444 FORMAT_DDDD, 1445 DATE, 1446 "dddd", 1447 null, 1448 "Display the day as a full name (Sunday - Saturday)."), 1449 nfe( 1450 FORMAT_DDDDD, 1451 DATE, 1452 "ddddd", 1453 null, 1454 "Display the date as a complete date (including day, month, and " 1455 + "year), formatted according to your system's short date format " 1456 + "setting. The default short date format is m/d/yy."), 1457 nfe( 1458 FORMAT_DDDDDD, 1459 DATE, 1460 "dddddd", 1461 null, 1462 "Display a date serial number as a complete date (including day, " 1463 + "month, and year) formatted according to the long date setting " 1464 + "recognized by your system. The default long date format is mmmm " 1465 + "dd, yyyy."), 1466 nfe( 1467 FORMAT_W, 1468 DATE, 1469 "w", 1470 null, 1471 "Display the day of the week as a number (1 for Sunday through 7 " 1472 + "for Saturday)."), 1473 nfe( 1474 FORMAT_WW, 1475 DATE, 1476 "ww", 1477 null, 1478 "Display the week of the year as a number (1 - 53)."), 1479 nfe( 1480 FORMAT_M, 1481 DATE | SPECIAL, 1482 "m", 1483 null, 1484 "Display the month as a number without a leading zero (1 - 12). If " 1485 + "m immediately follows h or hh, the minute rather than the month " 1486 + "is displayed."), 1487 nfe( 1488 FORMAT_MM, 1489 DATE | SPECIAL, 1490 "mm", 1491 null, 1492 "Display the month as a number with a leading zero (01 - 12). If m " 1493 + "immediately follows h or hh, the minute rather than the month " 1494 + "is displayed."), 1495 nfe( 1496 FORMAT_MMM_LOWER, 1497 DATE, 1498 "mmm", 1499 null, 1500 "Display the month as an abbreviation (Jan - Dec)."), 1501 nfe( 1502 FORMAT_MMMM_LOWER, 1503 DATE, 1504 "mmmm", 1505 null, 1506 "Display the month as a full month name (January - December)."), 1507 nfe( 1508 FORMAT_MMM_UPPER, 1509 DATE, 1510 "MMM", 1511 null, 1512 "Display the month as an abbreviation (Jan - Dec)."), 1513 nfe( 1514 FORMAT_MMMM_UPPER, 1515 DATE, 1516 "MMMM", 1517 null, 1518 "Display the month as a full month name (January - December)."), 1519 nfe( 1520 FORMAT_Q, 1521 DATE, 1522 "q", 1523 null, 1524 "Display the quarter of the year as a number (1 - 4)."), 1525 nfe( 1526 FORMAT_Y, 1527 DATE, 1528 "y", 1529 null, 1530 "Display the day of the year as a number (1 - 366)."), 1531 nfe( 1532 FORMAT_YY, 1533 DATE, 1534 "yy", 1535 null, 1536 "Display the year as a 2-digit number (00 - 99)."), 1537 nfe( 1538 FORMAT_YYYY, 1539 DATE, 1540 "yyyy", 1541 null, 1542 "Display the year as a 4-digit number (100 - 9999)."), 1543 nfe( 1544 FORMAT_H, 1545 DATE, 1546 "h", 1547 null, 1548 "Display the hour as a number without leading zeros (0 - 23)."), 1549 nfe( 1550 FORMAT_HH, 1551 DATE, 1552 "hh", 1553 null, 1554 "Display the hour as a number with leading zeros (00 - 23)."), 1555 nfe( 1556 FORMAT_N, 1557 DATE, 1558 "n", 1559 null, 1560 "Display the minute as a number without leading zeros (0 - 59)."), 1561 nfe( 1562 FORMAT_NN, 1563 DATE, 1564 "nn", 1565 null, 1566 "Display the minute as a number with leading zeros (00 - 59)."), 1567 nfe( 1568 FORMAT_S, 1569 DATE, 1570 "s", 1571 null, 1572 "Display the second as a number without leading zeros (0 - 59)."), 1573 nfe( 1574 FORMAT_SS, 1575 DATE, 1576 "ss", 1577 null, 1578 "Display the second as a number with leading zeros (00 - 59)."), 1579 nfe( 1580 FORMAT_TTTTT, 1581 DATE, 1582 "ttttt", 1583 null, 1584 "Display a time as a complete time (including hour, minute, and " 1585 + "second), formatted using the time separator defined by the time " 1586 + "format recognized by your system. A leading zero is displayed " 1587 + "if the leading zero option is selected and the time is before " 1588 + "10:00 A.M. or P.M. The default time format is h:mm:ss."), 1589 nfe( 1590 FORMAT_UPPER_AM_SOLIDUS_PM, 1591 DATE, 1592 "AM/PM", 1593 null, 1594 "Use the 12-hour clock and display an uppercase AM with any hour " 1595 + "before noon; display an uppercase PM with any hour between noon and 11:59 P.M."), 1596 nfe( 1597 FORMAT_LOWER_AM_SOLIDUS_PM, 1598 DATE, 1599 "am/pm", 1600 null, 1601 "Use the 12-hour clock and display a lowercase AM with any hour " 1602 + "before noon; display a lowercase PM with any hour between noon " 1603 + "and 11:59 P.M."), 1604 nfe( 1605 FORMAT_UPPER_A_SOLIDUS_P, 1606 DATE, 1607 "A/P", 1608 null, 1609 "Use the 12-hour clock and display an uppercase A with any hour " 1610 + "before noon; display an uppercase P with any hour between noon " 1611 + "and 11:59 P.M."), 1612 nfe( 1613 FORMAT_LOWER_A_SOLIDUS_P, 1614 DATE, 1615 "a/p", 1616 null, 1617 "Use the 12-hour clock and display a lowercase A with any hour " 1618 + "before noon; display a lowercase P with any hour between noon " 1619 + "and 11:59 P.M."), 1620 nfe( 1621 FORMAT_AMPM, 1622 DATE, 1623 "AMPM", 1624 null, 1625 "Use the 12-hour clock and display the AM string literal as " 1626 + "defined by your system with any hour before noon; display the " 1627 + "PM string literal as defined by your system with any hour " 1628 + "between noon and 11:59 P.M. AMPM can be either uppercase or " 1629 + "lowercase, but the case of the string displayed matches the " 1630 + "string as defined by your system settings. The default format " 1631 + "is AM/PM."), 1632 nfe( 1633 FORMAT_0, 1634 NUMERIC | SPECIAL, 1635 "0", 1636 "Digit placeholder", 1637 "Display a digit or a zero. If the expression has a digit in the " 1638 + "position where the 0 appears in the format string, display it; " 1639 + "otherwise, display a zero in that position. If the number has " 1640 + "fewer digits than there are zeros (on either side of the " 1641 + "decimal) in the format expression, display leading or trailing " 1642 + "zeros. If the number has more digits to the right of the " 1643 + "decimal separator than there are zeros to the right of the " 1644 + "decimal separator in the format expression, round the number to " 1645 + "as many decimal places as there are zeros. If the number has " 1646 + "more digits to the left of the decimal separator than there are " 1647 + "zeros to the left of the decimal separator in the format " 1648 + "expression, display the extra digits without modification."), 1649 nfe( 1650 FORMAT_POUND, 1651 NUMERIC | SPECIAL, 1652 "#", 1653 "Digit placeholder", 1654 "Display a digit or nothing. If the expression has a digit in the " 1655 + "position where the # appears in the format string, display it; " 1656 + "otherwise, display nothing in that position. This symbol works " 1657 + "like the 0 digit placeholder, except that leading and trailing " 1658 + "zeros aren't displayed if the number has the same or fewer " 1659 + "digits than there are # characters on either side of the " 1660 + "decimal separator in the format expression."), 1661 nfe( 1662 FORMAT_DECIMAL, 1663 NUMERIC | SPECIAL, 1664 ".", 1665 "Decimal placeholder", 1666 "In some locales, a comma is used as the decimal separator. The " 1667 + "decimal placeholder determines how many digits are displayed to " 1668 + "the left and right of the decimal separator. If the format " 1669 + "expression contains only number signs to the left of this " 1670 + "symbol, numbers smaller than 1 begin with a decimal separator. " 1671 + "If you always want a leading zero displayed with fractional " 1672 + "numbers, use 0 as the first digit placeholder to the left of " 1673 + "the decimal separator instead. The actual character used as a " 1674 + "decimal placeholder in the formatted output depends on the " 1675 + "Number Format recognized by your system."), 1676 nfe( 1677 FORMAT_PERCENT, 1678 NUMERIC, 1679 "%", 1680 "Percent placeholder", 1681 "The expression is multiplied by 100. The percent character (%) is " 1682 + "inserted in the position where it appears in the format " 1683 + "string."), 1684 nfe( 1685 FORMAT_THOUSEP, 1686 NUMERIC | SPECIAL, 1687 ",", 1688 "Thousand separator", 1689 "In some locales, a period is used as a thousand separator. The " 1690 + "thousand separator separates thousands from hundreds within a " 1691 + "number that has four or more places to the left of the decimal " 1692 + "separator. Standard use of the thousand separator is specified " 1693 + "if the format contains a thousand separator surrounded by digit " 1694 + "placeholders (0 or #). Two adjacent thousand separators or a " 1695 + "thousand separator immediately to the left of the decimal " 1696 + "separator (whether or not a decimal is specified) means \"scale " 1697 + "the number by dividing it by 1000, rounding as needed.\" You " 1698 + "can scale large numbers using this technique. For example, you " 1699 + "can use the format string \"##0,,\" to represent 100 million as " 1700 + "100. Numbers smaller than 1 million are displayed as 0. Two " 1701 + "adjacent thousand separators in any position other than " 1702 + "immediately to the left of the decimal separator are treated " 1703 + "simply as specifying the use of a thousand separator. The " 1704 + "actual character used as the thousand separator in the " 1705 + "formatted output depends on the Number Format recognized by " 1706 + "your system."), 1707 nfe( 1708 FORMAT_TIMESEP, 1709 DATE | SPECIAL, 1710 ":", 1711 "Time separator", 1712 "In some locales, other characters may be used to represent the " 1713 + "time separator. The time separator separates hours, minutes, " 1714 + "and seconds when time values are formatted. The actual " 1715 + "character used as the time separator in formatted output is " 1716 + "determined by your system settings."), 1717 nfe( 1718 FORMAT_DATESEP, 1719 DATE | SPECIAL, 1720 "/", 1721 "Date separator", 1722 "In some locales, other characters may be used to represent the " 1723 + "date separator. The date separator separates the day, month, " 1724 + "and year when date values are formatted. The actual character " 1725 + "used as the date separator in formatted output is determined by " 1726 + "your system settings."), 1727 nfe( 1728 FORMAT_E_MINUS_UPPER, 1729 NUMERIC | SPECIAL, 1730 "E-", 1731 "Scientific format", 1732 "If the format expression contains at least one digit placeholder " 1733 + "(0 or #) to the right of E-, E+, e-, or e+, the number is " 1734 + "displayed in scientific format and E or e is inserted between " 1735 + "the number and its exponent. The number of digit placeholders " 1736 + "to the right determines the number of digits in the exponent. " 1737 + "Use E- or e- to place a minus sign next to negative exponents. " 1738 + "Use E+ or e+ to place a minus sign next to negative exponents " 1739 + "and a plus sign next to positive exponents."), 1740 nfe( 1741 FORMAT_E_PLUS_UPPER, 1742 NUMERIC | SPECIAL, 1743 "E+", 1744 "Scientific format", 1745 "See E-."), 1746 nfe( 1747 FORMAT_E_MINUS_LOWER, 1748 NUMERIC | SPECIAL, 1749 "e-", 1750 "Scientific format", 1751 "See E-."), 1752 nfe( 1753 FORMAT_E_PLUS_LOWER, 1754 NUMERIC | SPECIAL, 1755 "e+", 1756 "Scientific format", 1757 "See E-."), 1758 nfe( 1759 FORMAT_LITERAL, 1760 GENERAL, 1761 "-", 1762 "Display a literal character", 1763 "To display a character other than one of those listed, precede it " 1764 + "with a backslash (\\) or enclose it in double quotation marks " 1765 + "(\" \")."), 1766 nfe( 1767 FORMAT_LITERAL, 1768 GENERAL, 1769 "+", 1770 "Display a literal character", 1771 "See -."), 1772 nfe( 1773 FORMAT_LITERAL, 1774 GENERAL, 1775 "$", 1776 "Display a literal character", 1777 "See -."), 1778 nfe( 1779 FORMAT_LITERAL, 1780 GENERAL, 1781 "(", 1782 "Display a literal character", 1783 "See -."), 1784 nfe( 1785 FORMAT_LITERAL, 1786 GENERAL, 1787 ")", 1788 "Display a literal character", 1789 "See -."), 1790 nfe( 1791 FORMAT_LITERAL, 1792 GENERAL, 1793 " ", 1794 "Display a literal character", 1795 "See -."), 1796 nfe( 1797 FORMAT_BACKSLASH, 1798 GENERAL | SPECIAL, 1799 "\\", 1800 "Display the next character in the format string", 1801 "Many characters in the format expression have a special meaning " 1802 + "and can't be displayed as literal characters unless they are " 1803 + "preceded by a backslash. The backslash itself isn't displayed. " 1804 + "Using a backslash is the same as enclosing the next character " 1805 + "in double quotation marks. To display a backslash, use two " 1806 + "backslashes (\\). Examples of characters that can't be " 1807 + "displayed as literal characters are the date- and " 1808 + "time-formatting characters (a, c, d, h, m, n, p, q, s, t, w, y, " 1809 + "and /:), the numeric-formatting characters (#, 0, %, E, e, " 1810 + "comma, and period), and the string-formatting characters (@, &, " 1811 + "<, >, and !)."), 1812 nfe( 1813 FORMAT_QUOTE, 1814 GENERAL | SPECIAL, 1815 "\"", 1816 "Display the string inside the double quotation marks", 1817 "To include a string in format from within code, you must use " 1818 + "Chr(34) to enclose the text (34 is the character code for a " 1819 + "double quotation mark)."), 1820 nfe( 1821 FORMAT_CHARACTER_OR_SPACE, 1822 STRING, 1823 "@", 1824 "Character placeholder", 1825 "Display a character or a space. If the string has a character in " 1826 + "the position where the @ appears in the format string, display " 1827 + "it; otherwise, display a space in that position. Placeholders " 1828 + "are filled from right to left unless there is an ! character in " 1829 + "the format string. See below."), 1830 nfe( 1831 FORMAT_CHARACTER_OR_NOTHING, 1832 STRING, 1833 "&", 1834 "Character placeholder", 1835 "Display a character or nothing. If the string has a character in " 1836 + "the position where the & appears, display it; otherwise, " 1837 + "display nothing. Placeholders are filled from right to left " 1838 + "unless there is an ! character in the format string. See " 1839 + "below."), 1840 nfe( 1841 FORMAT_LOWER, 1842 STRING | SPECIAL, 1843 "<", 1844 "Force lowercase", 1845 "Display all characters in lowercase format."), 1846 nfe( 1847 FORMAT_UPPER, 1848 STRING | SPECIAL, 1849 ">", 1850 "Force uppercase", 1851 "Display all characters in uppercase format."), 1852 nfe( 1853 FORMAT_FILL_FROM_LEFT, 1854 STRING | SPECIAL, 1855 "!", 1856 "Force left to right fill of placeholders", 1857 "The default is to fill from right to left."), 1858 nfe( 1859 FORMAT_SEMI, 1860 GENERAL | SPECIAL, 1861 ";", 1862 "Separates format strings for different kinds of values", 1863 "If there is one section, the format expression applies to all " 1864 + "values. If there are two sections, the first section applies " 1865 + "to positive values and zeros, the second to negative values. If " 1866 + "there are three sections, the first section applies to positive " 1867 + "values, the second to negative values, and the third to zeros. " 1868 + "If there are four sections, the first section applies to " 1869 + "positive values, the second to negative values, the third to " 1870 + "zeros, and the fourth to Null values."), 1871 nfe( 1872 FORMAT_INTL_CURRENCY, 1873 NUMERIC | SPECIAL, 1874 intlCurrencySymbol + "", 1875 null, 1876 "Display the locale's currency symbol."), 1877 nfe( 1878 FORMAT_USD, 1879 GENERAL, 1880 "USD", 1881 null, 1882 "Display USD (U.S. Dollars)."), 1883 nfe( 1884 FORMAT_GENERAL_NUMBER, 1885 NUMERIC | SPECIAL, 1886 "General Number", 1887 null, 1888 "Shows numbers as entered."), 1889 nfe( 1890 FORMAT_GENERAL_DATE, 1891 DATE | SPECIAL, 1892 "General Date", 1893 null, 1894 "Shows date and time if expression contains both. If expression is " 1895 + "only a date or a time, the missing information is not " 1896 + "displayed."), 1897 }; 1898 1899 // Named formats. todo: Supply the translation strings. 1900 private enum MacroToken { 1901 CURRENCY( 1902 "Currency", 1903 null, 1904 "Shows currency values according to the locale's CurrencyFormat. " 1905 + "Negative numbers are inside parentheses."), 1906 FIXED( 1907 "Fixed", "0", "Shows at least one digit."), 1908 STANDARD( 1909 "Standard", "#,##0", "Uses a thousands separator."), 1910 PERCENT( 1911 "Percent", 1912 "0.00%", 1913 "Multiplies the value by 100 with a percent sign at the end."), 1914 SCIENTIFIC( 1915 "Scientific", "0.00e+00", "Uses standard scientific notation."), 1916 LONG_DATE( 1917 "Long Date", 1918 "dddd, mmmm dd, yyyy", 1919 "Uses the Long Date format specified in the Regional Settings " 1920 + "dialog box of the Microsoft Windows Control Panel."), 1921 MEDIUM_DATE( 1922 "Medium Date", 1923 "dd-mmm-yy", 1924 "Uses the dd-mmm-yy format (for example, 03-Apr-93)"), 1925 SHORT_DATE( 1926 "Short Date", 1927 "m/d/yy", 1928 "Uses the Short Date format specified in the Regional Settings " 1929 + "dialog box of the Windows Control Panel."), 1930 LONG_TIME( 1931 "Long Time", 1932 "h:mm:ss AM/PM", 1933 "Shows the hour, minute, second, and \"AM\" or \"PM\" using the " 1934 + "h:mm:ss format."), 1935 MEDIUM_TIME( 1936 "Medium Time", 1937 "h:mm AM/PM", 1938 "Shows the hour, minute, and \"AM\" or \"PM\" using the \"hh:mm " 1939 + "AM/PM\" format."), 1940 SHORT_TIME( 1941 "Short Time", 1942 "hh:mm", 1943 "Shows the hour and minute using the hh:mm format."), 1944 YES_NO( 1945 "Yes/No", 1946 "\\Y\\e\\s;\\Y\\e\\s;\\N\\o;\\N\\o", 1947 "Any nonzero numeric value (usually - 1) is Yes. Zero is No."), 1948 TRUE_FALSE( 1949 "True/False", 1950 "\\T\\r\\u\\e;\\T\\r\\u\\e;\\F\\a\\l\\s\\e;\\F\\a\\l\\s\\e", 1951 "Any nonzero numeric value (usually - 1) is True. Zero is False."), 1952 ON_OFF( 1953 "On/Off", 1954 "\\O\\n;\\O\\n;\\O\\f\\f;\\O\\f\\f", 1955 "Any nonzero numeric value (usually - 1) is On. Zero is Off."); 1956 1957 /** 1958 * Maps macro token names with their related object. Used 1959 * to fast-resolve a macro token without iterating. 1960 */ 1961 private static final Map<String, MacroToken> MAP = 1962 new HashMap<String, MacroToken>(); 1963 1964 static { 1965 for (MacroToken macroToken : values()) { 1966 MAP.put(macroToken.token, macroToken); 1967 } 1968 } 1969 1970 MacroToken(String token, String translation, String description) { 1971 this.token = token; 1972 this.translation = translation; 1973 this.description = description; 1974 assert name().equals( 1975 token 1976 .replace(',', '_') 1977 .replace(' ', '_') 1978 .replace('/', '_') 1979 .toUpperCase()); 1980 assert (translation == null) == name().equals("CURRENCY"); 1981 } 1982 1983 final String token; 1984 final String translation; 1985 final String description; 1986 1987 static String expand(FormatLocale locale, String formatString) { 1988 final MacroToken macroToken = MAP.get(formatString); 1989 if (macroToken == null) { 1990 return formatString; 1991 } 1992 if (macroToken == MacroToken.CURRENCY) { 1993 // e.g. "$#,##0.00;($#,##0.00)" 1994 return locale.currencyFormat 1995 + ";(" + locale.currencyFormat + ")"; 1996 } else { 1997 return macroToken.translation; 1998 } 1999 } 2000 } 2001 2002 /** 2003 * Constructs a <code>Format</code> in a specific locale. 2004 * 2005 * @param formatString the format string; see 2006 * <a href="http://www.apostate.com/programming/vb-format.html">this 2007 * description</a> for more details 2008 * @param locale The locale 2009 */ 2010 public Format(String formatString, Locale locale) 2011 { 2012 this(formatString, getBestFormatLocale(locale)); 2013 } 2014 2015 /** 2016 * Constructs a <code>Format</code> in a specific locale. 2017 * 2018 * @param formatString the format string; see 2019 * <a href="http://www.apostate.com/programming/vb-format.html">this 2020 * description</a> for more details 2021 * @param locale The locale 2022 * 2023 * @see FormatLocale 2024 * @see #createLocale 2025 */ 2026 public Format(String formatString, FormatLocale locale) 2027 { 2028 if (formatString == null) { 2029 formatString = ""; 2030 } 2031 this.formatString = formatString; 2032 if (locale == null) { 2033 locale = locale_US; 2034 } 2035 this.locale = locale; 2036 2037 List<BasicFormat> alternateFormatList = new ArrayList<BasicFormat>(); 2038 FormatType[] formatType = {null}; 2039 while (formatString.length() > 0) { 2040 formatString = parseFormatString( 2041 formatString, alternateFormatList, formatType); 2042 } 2043 2044 // If the format string is empty, use a Java format. 2045 // Later entries in the formats list default to the first (e.g. 2046 // "#.00;;Nil"), but the first entry must be set. 2047 if (alternateFormatList.size() == 0 2048 || alternateFormatList.get(0) == null) 2049 { 2050 format = new JavaFormat(locale.locale); 2051 } else if (alternateFormatList.size() == 1 2052 && (formatType[0] == FormatType.DATE 2053 || formatType[0] == FormatType.STRING)) 2054 { 2055 format = alternateFormatList.get(0); 2056 } else { 2057 BasicFormat[] alternateFormats = 2058 alternateFormatList.toArray( 2059 new BasicFormat[alternateFormatList.size()]); 2060 format = new AlternateFormat(alternateFormats, locale); 2061 } 2062 } 2063 2064 /** 2065 * Constructs a <code>Format</code> in a specific locale, or retrieves 2066 * one from the cache if one already exists. 2067 * 2068 * <p>If the number of entries in the cache exceeds {@link #CacheLimit}, 2069 * replaces the eldest entry in the cache. 2070 * 2071 * @param formatString the format string; see 2072 * <a href="http://www.apostate.com/programming/vb-format.html">this 2073 * description</a> for more details 2074 * 2075 * @return format for given format string in given locale 2076 */ 2077 public static Format get(String formatString, Locale locale) { 2078 String key = formatString + "@@@" + locale; 2079 Format format = cache.get(key); 2080 if (format == null) { 2081 synchronized (cache) { 2082 format = cache.get(key); 2083 if (format == null) { 2084 format = new Format(formatString, locale); 2085 cache.put(key, format); 2086 } 2087 } 2088 } 2089 return format; 2090 } 2091 2092 /** 2093 * Create a {@link FormatLocale} object characterized by the given 2094 * properties. 2095 * 2096 * @param thousandSeparator the character used to separate thousands in 2097 * numbers, or ',' by default. For example, 12345 is '12,345 in English, 2098 * '12.345 in French. 2099 * @param decimalPlaceholder the character placed between the integer and 2100 * the fractional part of decimal numbers, or '.' by default. For 2101 * example, 12.34 is '12.34' in English, '12,34' in French. 2102 * @param dateSeparator the character placed between the year, month and 2103 * day of a date such as '12/07/2001', or '/' by default. 2104 * @param timeSeparator the character placed between the hour, minute and 2105 * second value of a time such as '1:23:45 AM', or ':' by default. 2106 * @param daysOfWeekShort Short forms of the days of the week. 2107 * The array is 1-based, because position 2108 * {@link Calendar#SUNDAY} (= 1) must hold Sunday, etc. 2109 * The array must have 8 elements. 2110 * For example {"", "Sun", "Mon", ..., "Sat"}. 2111 * @param daysOfWeekLong Long forms of the days of the week. 2112 * The array is 1-based, because position 2113 * {@link Calendar#SUNDAY} must hold Sunday, etc. 2114 * The array must have 8 elements. 2115 * For example {"", "Sunday", ..., "Saturday"}. 2116 * @param monthsShort Short forms of the months of the year. 2117 * The array is 0-based, because position 2118 * {@link Calendar#JANUARY} (= 0) holds January, etc. 2119 * For example {"Jan", ..., "Dec", ""}. 2120 * @param monthsLong Long forms of the months of the year. 2121 * The array is 0-based, because position 2122 * {@link Calendar#JANUARY} (= 0) holds January, etc. 2123 * For example {"January", ..., "December", ""}. 2124 * @param locale if this is not null, register that the constructed 2125 * <code>FormatLocale</code> is the default for <code>locale</code> 2126 */ 2127 public static FormatLocale createLocale( 2128 char thousandSeparator, 2129 char decimalPlaceholder, 2130 String dateSeparator, 2131 String timeSeparator, 2132 String currencySymbol, 2133 String currencyFormat, 2134 String[] daysOfWeekShort, 2135 String[] daysOfWeekLong, 2136 String[] monthsShort, 2137 String[] monthsLong, 2138 Locale locale) 2139 { 2140 FormatLocale formatLocale = new FormatLocale( 2141 thousandSeparator, decimalPlaceholder, dateSeparator, 2142 timeSeparator, currencySymbol, currencyFormat, daysOfWeekShort, 2143 daysOfWeekLong, monthsShort, monthsLong, locale); 2144 if (locale != null) { 2145 registerFormatLocale(formatLocale, locale); 2146 } 2147 return formatLocale; 2148 } 2149 2150 public static FormatLocale createLocale(Locale locale) 2151 { 2152 final DecimalFormatSymbols decimalSymbols = 2153 new DecimalFormatSymbols(locale); 2154 final DateFormatSymbols dateSymbols = new DateFormatSymbols(locale); 2155 2156 Calendar calendar = Calendar.getInstance(locale); 2157 calendar.set(1969, Calendar.DECEMBER, 31, 0, 0, 0); 2158 final Date date = calendar.getTime(); 2159 2160 final java.text.DateFormat dateFormat = 2161 java.text.DateFormat.getDateInstance( 2162 java.text.DateFormat.SHORT, locale); 2163 final String dateValue = dateFormat.format(date); // "12/31/69" 2164 String dateSeparator = dateValue.substring(2, 3); // "/" 2165 2166 final java.text.DateFormat timeFormat = 2167 java.text.DateFormat.getTimeInstance( 2168 java.text.DateFormat.SHORT, locale); 2169 final String timeValue = timeFormat.format(date); // "12:00:00" 2170 String timeSeparator = timeValue.substring(2, 3); // ":" 2171 2172 // Deduce the locale's currency format. 2173 // For example, US is "$#,###.00"; France is "#,###-00FF". 2174 final NumberFormat currencyFormat = 2175 NumberFormat.getCurrencyInstance(locale); 2176 final String currencyValue = currencyFormat.format(123456.78); 2177 String currencyLeft = 2178 currencyValue.substring(0, currencyValue.indexOf("1")); 2179 String currencyRight = 2180 currencyValue.substring(currencyValue.indexOf("8") + 1); 2181 StringBuilder buf = new StringBuilder(); 2182 buf.append(currencyLeft); 2183 int minimumIntegerDigits = currencyFormat.getMinimumIntegerDigits(); 2184 for (int i = Math.max(minimumIntegerDigits, 4) - 1; i >= 0; --i) { 2185 buf.append(i < minimumIntegerDigits ? '0' : '#'); 2186 if (i % 3 == 0 && i > 0) { 2187 buf.append(','); 2188 } 2189 } 2190 if (currencyFormat.getMaximumFractionDigits() > 0) { 2191 buf.append('.'); 2192 appendTimes(buf, '0', currencyFormat.getMinimumFractionDigits()); 2193 appendTimes( 2194 buf, '#', 2195 currencyFormat.getMaximumFractionDigits() 2196 - currencyFormat.getMinimumFractionDigits()); 2197 } 2198 buf.append(currencyRight); 2199 String currencyFormatString = buf.toString(); 2200 2201 // If the locale passed is only a language, Java cannot 2202 // resolve the currency symbol and will instead return 2203 // u00a4 (The international currency symbol). For those cases, 2204 // we use the default system locale currency symbol. 2205 String currencySymbol = decimalSymbols.getCurrencySymbol(); 2206 if (currencySymbol.equals(Format.intlCurrencySymbol + "")) { 2207 final DecimalFormatSymbols defaultDecimalSymbols = 2208 new DecimalFormatSymbols(Locale.getDefault()); 2209 currencySymbol = defaultDecimalSymbols.getCurrencySymbol(); 2210 } 2211 2212 return createLocale( 2213 decimalSymbols.getGroupingSeparator(), 2214 decimalSymbols.getDecimalSeparator(), 2215 dateSeparator, 2216 timeSeparator, 2217 currencySymbol, 2218 currencyFormatString, 2219 dateSymbols.getShortWeekdays(), 2220 dateSymbols.getWeekdays(), 2221 dateSymbols.getShortMonths(), 2222 dateSymbols.getMonths(), 2223 locale); 2224 } 2225 2226 private static void appendTimes(StringBuilder buf, char c, int i) { 2227 while (i-- > 0) { 2228 buf.append(c); 2229 } 2230 } 2231 2232 /** 2233 * Returns the {@link FormatLocale} which precisely matches {@link Locale}, 2234 * if any, or null if there is none. 2235 */ 2236 public static FormatLocale getFormatLocale(Locale locale) 2237 { 2238 if (locale == null) { 2239 locale = Locale.US; 2240 } 2241 String key = locale.toString(); 2242 return mapLocaleToFormatLocale.get(key); 2243 } 2244 2245 /** 2246 * Returns the best {@link FormatLocale} for a given {@link Locale}. 2247 * Never returns null, even if <code>locale</code> is null. 2248 */ 2249 public static synchronized FormatLocale getBestFormatLocale(Locale locale) 2250 { 2251 FormatLocale formatLocale; 2252 if (locale == null) { 2253 return locale_US; 2254 } 2255 String key = locale.toString(); 2256 // Look in the cache first. 2257 formatLocale = mapLocaleToFormatLocale.get(key); 2258 if (formatLocale == null) { 2259 // Not in the cache, so ask the factory. 2260 formatLocale = getFormatLocaleUsingFactory(locale); 2261 if (formatLocale == null) { 2262 formatLocale = locale_US; 2263 } 2264 // Add to cache. 2265 mapLocaleToFormatLocale.put(key, formatLocale); 2266 } 2267 return formatLocale; 2268 } 2269 2270 private static FormatLocale getFormatLocaleUsingFactory(Locale locale) 2271 { 2272 FormatLocale formatLocale; 2273 // Lookup full locale, e.g. "en-US-Boston" 2274 if (!locale.getVariant().equals("")) { 2275 formatLocale = createLocale(locale); 2276 if (formatLocale != null) { 2277 return formatLocale; 2278 } 2279 locale = new Locale(locale.getLanguage(), locale.getCountry()); 2280 } 2281 // Lookup language and country, e.g. "en-US" 2282 if (!locale.getCountry().equals("")) { 2283 formatLocale = createLocale(locale); 2284 if (formatLocale != null) { 2285 return formatLocale; 2286 } 2287 locale = new Locale(locale.getLanguage()); 2288 } 2289 // Lookup language, e.g. "en" 2290 formatLocale = createLocale(locale); 2291 if (formatLocale != null) { 2292 return formatLocale; 2293 } 2294 return null; 2295 } 2296 2297 /** 2298 * Registers a {@link FormatLocale} to a given {@link Locale}. Returns the 2299 * previous mapping. 2300 */ 2301 public static FormatLocale registerFormatLocale( 2302 FormatLocale formatLocale, Locale locale) 2303 { 2304 String key = locale.toString(); // e.g. "en_us_Boston" 2305 return mapLocaleToFormatLocale.put(key, formatLocale); 2306 } 2307 2308 // Values for variable numberState below. 2309 static final int NOT_IN_A_NUMBER = 0; 2310 static final int LEFT_OF_POINT = 1; 2311 static final int RIGHT_OF_POINT = 2; 2312 static final int RIGHT_OF_EXP = 3; 2313 2314 /** 2315 * Reads formatString up to the first semi-colon, or to the end if there 2316 * are no semi-colons. Adds a format to alternateFormatList, and returns 2317 * the remains of formatString. 2318 */ 2319 private String parseFormatString( 2320 String formatString, 2321 List<BasicFormat> alternateFormatList, 2322 FormatType[] formatTypeOut) 2323 { 2324 // Cache the original value 2325 final String originalFormatString = formatString; 2326 2327 // Where we are in a numeric format. 2328 int numberState = NOT_IN_A_NUMBER; 2329 StringBuilder ignored = new StringBuilder(); 2330 String prevIgnored = null; 2331 boolean haveSeenNumber = false; 2332 int digitsLeftOfPoint = 0, 2333 digitsRightOfPoint = 0, 2334 digitsRightOfExp = 0, 2335 zeroesLeftOfPoint = 0, 2336 zeroesRightOfPoint = 0, 2337 zeroesRightOfExp = 0; 2338 boolean useDecimal = false, 2339 useThouSep = false, 2340 fillFromRight = true; 2341 2342 // Whether to print numbers in decimal or exponential format. Valid 2343 // values are FORMAT_NULL, FORMAT_E_PLUS_LOWER, FORMAT_E_MINUS_LOWER, 2344 // FORMAT_E_PLUS_UPPER, FORMAT_E_MINUS_UPPER. 2345 int expFormat = FORMAT_NULL; 2346 2347 // todo: Parse the string for ;s 2348 2349 // Look for the format string in the table of named formats. 2350 formatString = MacroToken.expand(locale, formatString); 2351 2352 // Add a semi-colon to the end of the string so the end of the string 2353 // looks like the end of an alternate. 2354 if (!formatString.endsWith(";")) { 2355 formatString = formatString + ";"; 2356 } 2357 2358 // Scan through the format string for format elements. 2359 List<BasicFormat> formatList = new ArrayList<BasicFormat>(); 2360 List<Integer> thousands = new ArrayList<Integer>(); 2361 int decimalShift = 0; 2362 loop: 2363 while (formatString.length() > 0) { 2364 BasicFormat format = null; 2365 String newFormatString; 2366 final Token token = findToken(formatString, formatTypeOut[0]); 2367 if (token != null) { 2368 String matched = token.token; 2369 newFormatString = formatString.substring(matched.length()); 2370 if (token.isSpecial()) { 2371 switch (token.code) { 2372 case FORMAT_SEMI: 2373 break loop; 2374 2375 case FORMAT_POUND: 2376 switch (numberState) { 2377 case NOT_IN_A_NUMBER: 2378 numberState = LEFT_OF_POINT; 2379 // fall through 2380 case LEFT_OF_POINT: 2381 digitsLeftOfPoint++; 2382 break; 2383 case RIGHT_OF_POINT: 2384 digitsRightOfPoint++; 2385 break; 2386 case RIGHT_OF_EXP: 2387 digitsRightOfExp++; 2388 break; 2389 default: 2390 throw new Error(); 2391 } 2392 break; 2393 2394 case FORMAT_0: 2395 switch (numberState) { 2396 case NOT_IN_A_NUMBER: 2397 numberState = LEFT_OF_POINT; 2398 // fall through 2399 case LEFT_OF_POINT: 2400 zeroesLeftOfPoint++; 2401 break; 2402 case RIGHT_OF_POINT: 2403 zeroesRightOfPoint++; 2404 break; 2405 case RIGHT_OF_EXP: 2406 zeroesRightOfExp++; 2407 break; 2408 default: 2409 throw new Error(); 2410 } 2411 break; 2412 2413 case FORMAT_M: 2414 case FORMAT_MM: 2415 { 2416 // "m" and "mm" mean minute if immediately after 2417 // "h" or "hh"; month otherwise. 2418 boolean theyMeantMinute = false; 2419 int j = formatList.size() - 1; 2420 while (j >= 0) { 2421 BasicFormat prevFormat = formatList.get(j); 2422 if (prevFormat instanceof LiteralFormat) { 2423 // ignore boilerplate 2424 j--; 2425 } else if (prevFormat.code == FORMAT_H 2426 || prevFormat.code == FORMAT_HH) 2427 { 2428 theyMeantMinute = true; 2429 break; 2430 } else { 2431 theyMeantMinute = false; 2432 break; 2433 } 2434 } 2435 if (theyMeantMinute) { 2436 format = new DateFormat( 2437 (token.code == FORMAT_M 2438 ? FORMAT_N 2439 : FORMAT_NN), 2440 matched, 2441 locale, 2442 false); 2443 } else { 2444 format = token.makeFormat(locale); 2445 } 2446 break; 2447 } 2448 2449 case FORMAT_DECIMAL: 2450 { 2451 if (numberState == LEFT_OF_POINT) { 2452 decimalShift = 2453 fixThousands( 2454 thousands, formatString, decimalShift); 2455 } 2456 numberState = RIGHT_OF_POINT; 2457 useDecimal = true; 2458 break; 2459 } 2460 2461 case FORMAT_THOUSEP: 2462 { 2463 if (numberState == LEFT_OF_POINT) { 2464 // e.g. "#,##" 2465 useThouSep = true; 2466 thousands.add(formatString.length()); 2467 } else { 2468 // e.g. "ddd, mmm dd, yyy" 2469 format = token.makeFormat(locale); 2470 } 2471 break; 2472 } 2473 2474 case FORMAT_TIMESEP: 2475 { 2476 format = new LiteralFormat(locale.timeSeparator); 2477 break; 2478 } 2479 2480 case FORMAT_DATESEP: 2481 { 2482 format = new LiteralFormat(locale.dateSeparator); 2483 break; 2484 } 2485 2486 case FORMAT_BACKSLASH: 2487 { 2488 // Display the next character in the format string. 2489 String s; 2490 if (formatString.length() == 1) { 2491 // Backslash is the last character in the 2492 // string. 2493 s = ""; 2494 newFormatString = ""; 2495 } else { 2496 s = formatString.substring(1, 2); 2497 newFormatString = formatString.substring(2); 2498 } 2499 format = new LiteralFormat(s); 2500 break; 2501 } 2502 2503 case FORMAT_E_MINUS_UPPER: 2504 case FORMAT_E_PLUS_UPPER: 2505 case FORMAT_E_MINUS_LOWER: 2506 case FORMAT_E_PLUS_LOWER: 2507 { 2508 if (numberState == LEFT_OF_POINT) { 2509 decimalShift = 2510 fixThousands( 2511 thousands, formatString, decimalShift); 2512 } 2513 numberState = RIGHT_OF_EXP; 2514 expFormat = token.code; 2515 if (zeroesLeftOfPoint == 0 2516 && zeroesRightOfPoint == 0) 2517 { 2518 // We need a mantissa, so that format(123.45, 2519 // "E+") gives "1E+2", not "0E+2" or "E+2". 2520 zeroesLeftOfPoint = 1; 2521 } 2522 break; 2523 } 2524 2525 case FORMAT_QUOTE: 2526 { 2527 // Display the string inside the double quotation 2528 // marks. 2529 String s; 2530 int j = formatString.indexOf("\"", 1); 2531 if (j == -1) { 2532 // The string did not contain a closing quote. 2533 // Use the whole string. 2534 s = formatString.substring(1); 2535 newFormatString = ""; 2536 } else { 2537 // Take the string inside the quotes. 2538 s = formatString.substring(1, j); 2539 newFormatString = formatString.substring( 2540 j + 1); 2541 } 2542 format = new LiteralFormat(s); 2543 break; 2544 } 2545 2546 case FORMAT_UPPER: 2547 { 2548 format = 2549 new StringFormat( 2550 StringCase.UPPER, ">", locale.locale); 2551 break; 2552 } 2553 2554 case FORMAT_LOWER: 2555 { 2556 format = 2557 new StringFormat( 2558 StringCase.LOWER, "<", locale.locale); 2559 break; 2560 } 2561 2562 case FORMAT_FILL_FROM_LEFT: 2563 { 2564 fillFromRight = false; 2565 break; 2566 } 2567 2568 case FORMAT_GENERAL_NUMBER: 2569 { 2570 format = new JavaFormat(locale.locale); 2571 break; 2572 } 2573 2574 case FORMAT_GENERAL_DATE: 2575 { 2576 format = new JavaFormat(locale.locale); 2577 break; 2578 } 2579 2580 case FORMAT_INTL_CURRENCY: 2581 { 2582 format = new LiteralFormat(locale.currencySymbol); 2583 break; 2584 } 2585 2586 default: 2587 throw new Error(); 2588 } 2589 if (formatTypeOut[0] == null) { 2590 formatTypeOut[0] = token.getFormatType(); 2591 } 2592 if (format == null) { 2593 // If the special-case code does not set format, 2594 // we should not create a format element. (The 2595 // token probably caused some flag to be set.) 2596 ignored.append(matched); 2597 } else { 2598 prevIgnored = ignored.toString(); 2599 ignored.setLength(0); 2600 } 2601 } else { 2602 format = token.makeFormat(locale); 2603 } 2604 } else { 2605 // None of the standard format elements matched. Make the 2606 // current character into a literal. 2607 format = new LiteralFormat( 2608 formatString.substring(0, 1)); 2609 newFormatString = formatString.substring(1); 2610 } 2611 2612 if (format != null) { 2613 if (numberState != NOT_IN_A_NUMBER) { 2614 // Having seen a few number tokens, we're looking at a 2615 // non-number token. Create the number first. 2616 if (numberState == LEFT_OF_POINT) { 2617 decimalShift = 2618 fixThousands( 2619 thousands, formatString, decimalShift); 2620 } 2621 NumericFormat numericFormat = new NumericFormat( 2622 prevIgnored, locale, expFormat, digitsLeftOfPoint, 2623 zeroesLeftOfPoint, digitsRightOfPoint, 2624 zeroesRightOfPoint, digitsRightOfExp, zeroesRightOfExp, 2625 useDecimal, useThouSep, originalFormatString); 2626 formatList.add(numericFormat); 2627 numberState = NOT_IN_A_NUMBER; 2628 haveSeenNumber = true; 2629 } 2630 2631 formatList.add(format); 2632 if (formatTypeOut[0] == null) { 2633 formatTypeOut[0] = format.getFormatType(); 2634 } 2635 } 2636 2637 formatString = newFormatString; 2638 } 2639 2640 if (numberState != NOT_IN_A_NUMBER) { 2641 // We're still in a number. Create a number format. 2642 if (numberState == LEFT_OF_POINT) { 2643 decimalShift = 2644 fixThousands( 2645 thousands, formatString, decimalShift); 2646 } 2647 NumericFormat numericFormat = new NumericFormat( 2648 prevIgnored, locale, expFormat, digitsLeftOfPoint, 2649 zeroesLeftOfPoint, digitsRightOfPoint, zeroesRightOfPoint, 2650 digitsRightOfExp, zeroesRightOfExp, useDecimal, useThouSep, 2651 originalFormatString); 2652 formatList.add(numericFormat); 2653 numberState = NOT_IN_A_NUMBER; 2654 haveSeenNumber = true; 2655 } 2656 2657 if (formatString.startsWith(";")) { 2658 formatString = formatString.substring(1); 2659 } 2660 2661 // If they used some symbol like 'AM/PM' in the format string, tell all 2662 // date formats to use twelve hour clock. Likewise, figure out the 2663 // multiplier implied by their use of "%" or ",". 2664 boolean twelveHourClock = false; 2665 for (int i = 0; i < formatList.size(); i++) { 2666 switch (formatList.get(i).code) { 2667 case FORMAT_UPPER_AM_SOLIDUS_PM: 2668 case FORMAT_LOWER_AM_SOLIDUS_PM: 2669 case FORMAT_UPPER_A_SOLIDUS_P: 2670 case FORMAT_LOWER_A_SOLIDUS_P: 2671 case FORMAT_AMPM: 2672 twelveHourClock = true; 2673 break; 2674 2675 case FORMAT_PERCENT: 2676 // If "%" occurs, the number should be multiplied by 100. 2677 decimalShift += 2; 2678 break; 2679 2680 case FORMAT_THOUSEP: 2681 // If there is a thousands separator (",") immediately to the 2682 // left of the point, or at the end of the number, divide the 2683 // number by 1000. (Or by 1000^n if there are more than one.) 2684 if (haveSeenNumber 2685 && i + 1 < formatList.size()) 2686 { 2687 final BasicFormat nextFormat = formatList.get(i + 1); 2688 if (nextFormat.code != FORMAT_THOUSEP 2689 && nextFormat.code != FORMAT_0 2690 && nextFormat.code != FORMAT_POUND) 2691 { 2692 for (int j = i; 2693 j >= 0 && formatList.get(j).code == FORMAT_THOUSEP; 2694 j--) 2695 { 2696 decimalShift -= 3; 2697 formatList.remove(j); // ignore 2698 --i; 2699 } 2700 } 2701 } 2702 break; 2703 2704 default: 2705 } 2706 } 2707 2708 if (twelveHourClock) { 2709 for (int i = 0; i < formatList.size(); i++) { 2710 if (formatList.get(i) instanceof DateFormat) { 2711 ((DateFormat) formatList.get(i)).setTwelveHourClock(true); 2712 } 2713 } 2714 } 2715 2716 if (decimalShift != 0) { 2717 for (int i = 0; i < formatList.size(); i++) { 2718 if (formatList.get(i) instanceof NumericFormat) { 2719 ((NumericFormat) formatList.get(i)).decimalShift = 2720 decimalShift; 2721 } 2722 } 2723 } 2724 2725 // Merge adjacent literal formats. 2726 // 2727 // Must do this AFTER adjusting for percent. Otherwise '%' and following 2728 // '|' might be merged into a plain literal, and '%' would lose its 2729 // special powers. 2730 for (int i = 0; i < formatList.size(); ++i) { 2731 if (i > 0 2732 && formatList.get(i) instanceof LiteralFormat 2733 && formatList.get(i - 1) instanceof LiteralFormat) 2734 { 2735 formatList.set( 2736 i - 1, 2737 new LiteralFormat( 2738 ((LiteralFormat) formatList.get(i - 1)).s 2739 + ((LiteralFormat) formatList.get(i)).s)); 2740 formatList.remove(i); 2741 --i; 2742 } 2743 } 2744 2745 // Create a CompoundFormat containing all of the format elements. 2746 // This is the end of an alternate - or of the whole format string. 2747 // Push the current list of formats onto the list of alternates. 2748 2749 BasicFormat alternateFormat; 2750 switch (formatList.size()) { 2751 case 0: 2752 alternateFormat = null; 2753 break; 2754 case 1: 2755 alternateFormat = formatList.get(0); 2756 break; 2757 default: 2758 alternateFormat = 2759 new CompoundFormat( 2760 formatList.toArray( 2761 new BasicFormat[formatList.size()])); 2762 break; 2763 } 2764 alternateFormatList.add(alternateFormat); 2765 return formatString; 2766 } 2767 2768 private Token findToken(String formatString, FormatType formatType) { 2769 for (int i = tokens.length - 1; i > 0; i--) { 2770 final Token token = tokens[i]; 2771 if (formatString.startsWith(token.token) 2772 && token.compatibleWith(formatType)) 2773 { 2774 return token; 2775 } 2776 } 2777 return null; 2778 } 2779 2780 private int fixThousands( 2781 List<Integer> thousands, String formatString, int shift) 2782 { 2783 int offset = formatString.length() + 1; 2784 for (int i = thousands.size() - 1; i >= 0; i--) { 2785 Integer integer = thousands.get(i); 2786 thousands.set(i, integer - offset); 2787 ++offset; 2788 } 2789 while (thousands.size() > 0 2790 && thousands.get(thousands.size() - 1) == 0) 2791 { 2792 shift -= 3; 2793 thousands.remove(thousands.size() - 1); 2794 } 2795 return shift; 2796 } 2797 2798 public String format(Object o) 2799 { 2800 StringBuilder buf = new StringBuilder(); 2801 format(o, buf); 2802 return buf.toString(); 2803 } 2804 2805 private StringBuilder format(Object o, StringBuilder buf) { 2806 if (o == null) { 2807 format.formatNull(buf); 2808 } else { 2809 // For final classes, it is more efficient to switch using 2810 // class equality than using 'instanceof'. 2811 Class<? extends Object> clazz = o.getClass(); 2812 if (clazz == Double.class) { 2813 format.format((Double) o, buf); 2814 } else if (clazz == Float.class) { 2815 format.format((Float) o, buf); 2816 } else if (clazz == Integer.class) { 2817 format.format((Integer) o, buf); 2818 } else if (clazz == Long.class) { 2819 format.format((Long) o, buf); 2820 } else if (clazz == Short.class) { 2821 format.format((Short) o, buf); 2822 } else if (clazz == Byte.class) { 2823 format.format((Byte) o, buf); 2824 } else if (o instanceof BigDecimal) { 2825 format.format( 2826 ((BigDecimal) o).doubleValue(), buf); 2827 } else if (o instanceof BigInteger) { 2828 format.format( 2829 ((BigInteger) o).longValue(), buf); 2830 } else if (clazz == String.class) { 2831 format.format((String) o, buf); 2832 } else if (o instanceof java.util.Date) { 2833 // includes java.sql.Date, java.sql.Time and java.sql.Timestamp 2834 format.format((Date) o, buf); 2835 } else if (o instanceof Calendar) { 2836 format.format((Calendar) o, buf); 2837 } else { 2838 buf.append(o.toString()); 2839 } 2840 } 2841 return buf; 2842 } 2843 2844 public String getFormatString() 2845 { 2846 return formatString; 2847 } 2848 2849 private static void shift( 2850 MondrianFloatingDecimal fd, 2851 int i) 2852 { 2853 if (fd.isExceptional 2854 || fd.nDigits == 1 && fd.digits[0] == '0') 2855 { 2856 ; // don't multiply zero 2857 } else { 2858 fd.decExponent += i; 2859 } 2860 } 2861 2862 /** Formats a floating decimal to a given buffer. */ 2863 private static void formatFd0( 2864 MondrianFloatingDecimal fd, 2865 StringBuilder buf, 2866 int minDigitsLeftOfDecimal, 2867 char decimalChar, // '.' or ',' 2868 int minDigitsRightOfDecimal, 2869 int maxDigitsRightOfDecimal, 2870 char expChar, // 'E' or 'e' 2871 boolean expSign, // whether to print '+' if exp is positive 2872 int minExpDigits, // minimum digits in exponent 2873 char thousandChar, // ',' or '.', or 0 2874 boolean useDecimal, 2875 ArrayStack<Integer> thousandSeparatorPositions) 2876 { 2877 // char result[] = new char[nDigits + 10]; // crashes for 1.000.000,00 2878 // the result length does *not* depend from nDigits 2879 // it is : decExponent 2880 // +maxDigitsRightOfDecimal 2881 // + 10 (for decimal point and sign or -Infinity) 2882 // +decExponent/3 (for the thousand separators) 2883 int resultLen = 2884 10 + Math.abs(fd.decExponent) * 4 / 3 + maxDigitsRightOfDecimal; 2885 char result[] = new char[resultLen]; 2886 int i = formatFd1( 2887 fd, 2888 result, 2889 0, 2890 minDigitsLeftOfDecimal, 2891 decimalChar, 2892 minDigitsRightOfDecimal, 2893 maxDigitsRightOfDecimal, 2894 expChar, 2895 expSign, 2896 minExpDigits, 2897 thousandChar, 2898 useDecimal, 2899 thousandSeparatorPositions); 2900 buf.append(result, 0, i); 2901 } 2902 2903 /** Formats a floating decimal to a given char array. */ 2904 private static int formatFd1( 2905 MondrianFloatingDecimal fd, 2906 char result[], 2907 int i, 2908 int minDigitsLeftOfDecimal, 2909 char decimalChar, // '.' or ',' 2910 int minDigitsRightOfDecimal, 2911 int maxDigitsRightOfDecimal, 2912 char expChar, // 'E' or 'e' 2913 boolean expSign, // whether to print '+' if exp is positive 2914 int minExpDigits, // minimum digits in exponent 2915 char thousandChar, // ',' or '.' or 0 2916 boolean useDecimal, 2917 ArrayStack<Integer> thousandSeparatorPositions) 2918 { 2919 if (expChar != 0) { 2920 // Print the digits left of the 'E'. 2921 int oldExp = fd.decExponent; 2922 fd.decExponent = Math.min(minDigitsLeftOfDecimal, fd.nDigits); 2923 boolean oldIsNegative = fd.isNegative; 2924 fd.isNegative = false; 2925 i = formatFd2( 2926 fd, 2927 result, 2928 i, 2929 minDigitsLeftOfDecimal, 2930 decimalChar, 2931 minDigitsRightOfDecimal, 2932 maxDigitsRightOfDecimal, 2933 '\0', 2934 useDecimal, 2935 thousandSeparatorPositions); 2936 fd.decExponent = oldExp; 2937 fd.isNegative = oldIsNegative; 2938 2939 result[i++] = expChar; 2940 // Print the digits right of the 'E'. 2941 return fd.formatExponent(result, i, expSign, minExpDigits); 2942 } else { 2943 return formatFd2( 2944 fd, 2945 result, 2946 i, 2947 minDigitsLeftOfDecimal, 2948 decimalChar, 2949 minDigitsRightOfDecimal, 2950 maxDigitsRightOfDecimal, 2951 thousandChar, 2952 useDecimal, 2953 thousandSeparatorPositions); 2954 } 2955 } 2956 2957 static int formatFd2( 2958 MondrianFloatingDecimal fd, 2959 char result[], 2960 int i, 2961 int minDigitsLeftOfDecimal, 2962 char decimalChar, // '.' or ',' 2963 int minDigitsRightOfDecimal, 2964 int maxDigitsRightOfDecimal, 2965 char thousandChar, // ',' or '.' or 0 2966 boolean useDecimal, 2967 ArrayStack<Integer> thousandSeparatorPositions) 2968 { 2969 if (fd.isNegative) { 2970 result[i++] = '-'; 2971 } 2972 if (fd.isExceptional) { 2973 System.arraycopy(fd.digits, 0, result, i, fd.nDigits); 2974 return i + fd.nDigits; 2975 } 2976 // Build a new array of digits, padded with 0s at either end. For 2977 // example, here is the array we would build for 1234.56. 2978 // 2979 // | 0 0 1 2 3 . 4 5 6 0 0 | 2980 // | |- nDigits=6 -----------------------| | 2981 // | |- decExponent=3 -| | 2982 // |- minDigitsLeftOfDecimal=5 --| | 2983 // | |- minDigitsRightOfDecimal=5 --| 2984 // |- wholeDigits=5 -------------|- fractionDigits=5 -----------| 2985 // |- totalDigits=10 -------------------------------------------| 2986 // | |- maxDigitsRightOfDecimal=5 --| 2987 int wholeDigits = Math.max(fd.decExponent, minDigitsLeftOfDecimal), 2988 fractionDigits = Math.max( 2989 fd.nDigits - fd.decExponent, minDigitsRightOfDecimal), 2990 totalDigits = wholeDigits + fractionDigits; 2991 char[] digits2 = new char[totalDigits]; 2992 for (int j = 0; j < totalDigits; j++) { 2993 digits2[j] = '0'; 2994 } 2995 for (int j = 0; j < fd.nDigits; j++) { 2996 digits2[wholeDigits - fd.decExponent + j] = fd.digits[j]; 2997 } 2998 2999 // Now round. Suppose that we want to round 1234.56 to 1 decimal 3000 // place (that is, maxDigitsRightOfDecimal = 1). Then lastDigit 3001 // initially points to '5'. We find out that we need to round only 3002 // when we see that the next digit ('6') is non-zero. 3003 // 3004 // | 0 0 1 2 3 . 4 5 6 0 0 | 3005 // | | ^ | | 3006 // | maxDigitsRightOfDecimal=1 | 3007 int lastDigit = wholeDigits + maxDigitsRightOfDecimal; 3008 if (lastDigit < totalDigits) { 3009 // We need to truncate -- also round if the trailing digits are 3010 // 5000... or greater. 3011 int m = totalDigits; 3012 while (true) { 3013 m--; 3014 if (m < 0) { 3015 // The entire number was 9s. Re-allocate, so we can 3016 // prepend a '1'. 3017 wholeDigits++; 3018 totalDigits++; 3019 lastDigit++; 3020 char[] old = digits2; 3021 digits2 = new char[totalDigits]; 3022 digits2[0] = '1'; 3023 System.arraycopy(old, 0, digits2, 1, old.length); 3024 break; 3025 } else if (m == lastDigit) { 3026 char d = digits2[m]; 3027 digits2[m] = '0'; 3028 if (d < '5') { 3029 break; // no need to round 3030 } 3031 } else if (m > lastDigit) { 3032 digits2[m] = '0'; 3033 } else if (digits2[m] == '9') { 3034 digits2[m] = '0'; 3035 // do not break - we have to carry 3036 } else { 3037 digits2[m]++; 3038 break; // nothing to carry 3039 } 3040 } 3041 } 3042 3043 // Find the first non-zero digit and the last non-zero digit. 3044 int firstNonZero = wholeDigits, 3045 firstTrailingZero = 0; 3046 for (int j = 0; j < totalDigits; j++) { 3047 if (digits2[j] != '0') { 3048 if (j < firstNonZero) { 3049 firstNonZero = j; 3050 } 3051 firstTrailingZero = j + 1; 3052 } 3053 } 3054 3055 int firstDigitToPrint = firstNonZero; 3056 if (firstDigitToPrint > wholeDigits - minDigitsLeftOfDecimal) { 3057 firstDigitToPrint = wholeDigits - minDigitsLeftOfDecimal; 3058 } 3059 int lastDigitToPrint = firstTrailingZero; 3060 if (lastDigitToPrint > wholeDigits + maxDigitsRightOfDecimal) { 3061 lastDigitToPrint = wholeDigits + maxDigitsRightOfDecimal; 3062 } 3063 if (lastDigitToPrint < wholeDigits + minDigitsRightOfDecimal) { 3064 lastDigitToPrint = wholeDigits + minDigitsRightOfDecimal; 3065 } 3066 3067 if (thousandChar != '\0' 3068 && thousandSeparatorPositions.size() > 0) 3069 { 3070 // Now print the number. That will happen backwards, so we 3071 // store it temporarily and then invert. 3072 ArrayStack<Character> formattedWholeDigits = 3073 new ArrayStack<Character>(); 3074 // We need to keep track of how many digits we printed in the 3075 // current token. 3076 int nbInserted = 0; 3077 for (int j = wholeDigits - 1; j >= firstDigitToPrint; j--) { 3078 // Check if we need to insert another thousand separator 3079 if (nbInserted % thousandSeparatorPositions.peek() == 0 3080 && nbInserted > 0) 3081 { 3082 formattedWholeDigits.push(thousandChar); 3083 nbInserted = 0; 3084 // The last format token is kept because we re-apply it 3085 // until the end of the digits. 3086 if (thousandSeparatorPositions.size() > 1) { 3087 thousandSeparatorPositions.pop(); 3088 } 3089 } 3090 // Insert the next digit. 3091 formattedWholeDigits.push(digits2[j]); 3092 nbInserted++; 3093 } 3094 // We're done. Invert the print out and add it to 3095 // the result array. 3096 while (formattedWholeDigits.size() > 0) { 3097 result[i++] = formattedWholeDigits.pop(); 3098 } 3099 } else { 3100 // There are no thousand separators. Just put the 3101 // digits in the results array. 3102 for (int j = firstDigitToPrint; j < wholeDigits; j++) { 3103 result[i++] = digits2[j]; 3104 } 3105 } 3106 3107 if (wholeDigits < lastDigitToPrint 3108 || (useDecimal 3109 && wholeDigits == lastDigitToPrint)) 3110 { 3111 result[i++] = decimalChar; 3112 } 3113 for (int j = wholeDigits; j < lastDigitToPrint; j++) { 3114 result[i++] = digits2[j]; 3115 } 3116 return i; 3117 } 3118 3119 private enum FormatType { 3120 STRING, 3121 DATE, 3122 NUMERIC 3123 } 3124 3125 private static class DummyDecimalFormat extends DecimalFormat { 3126 private FieldPosition pos; 3127 3128 public StringBuffer format( 3129 double number, 3130 StringBuffer result, 3131 FieldPosition fieldPosition) 3132 { 3133 pos = fieldPosition; 3134 return result; 3135 } 3136 } 3137 3138 /** Specification for MondrianFloatingDecimal. */ 3139 private static class MondrianFloatingDecimalSpec { 3140 boolean isExceptional; 3141 boolean isNegative; 3142 int decExponent; 3143 char digits[]; 3144 int nDigits; 3145 3146 /** Creates a floating decimal with a given value. */ 3147 MondrianFloatingDecimalSpec(double n) { 3148 } 3149 3150 /** 3151 * Appends {@link #decExponent} to result string. Returns i plus the 3152 * number of chars written. 3153 * 3154 * <p>Implementation may assume that exponent has 3 or fewer digits.</p> 3155 * 3156 * <p>For example, given {@code decExponent} = 2, 3157 * {@code formatExponent(result, 5, true, 2)} 3158 * will write '0' into result[5] 3159 * and '2' into result[6] and return 7.</p> 3160 * 3161 * @param result Result buffer 3162 * @param i Initial offset into result buffer 3163 * @param expSign Whether to print a '+' sign if exponent is positive 3164 * (always prints '-' if negative) 3165 * @param minExpDigits Minimum number of digits to write 3166 * @return Offset into result buffer after writing chars 3167 */ 3168 int formatExponent( 3169 char[] result, 3170 int i, 3171 boolean expSign, 3172 int minExpDigits) 3173 { 3174 return i; 3175 } 3176 3177 /** 3178 * Handles an exceptional number. If {@link #isExceptional} is false, 3179 * does nothing. If {@link #isExceptional} is true, appends the contents 3180 * of {@link #digits} to result starting from i and returns the 3181 * incremented i. 3182 * 3183 * @param result Result buffer 3184 * @param i Initial offset into result buffer 3185 * @return Offset into result buffer after writing chars 3186 */ 3187 int handleExceptional(char[] result, int i) { 3188 return i; 3189 } 3190 3191 /** 3192 * Handles a negative number. If {@link #isNegative}, appends '-' to 3193 * result at i and returns i + 1; otherwise does nothing and returns i. 3194 * 3195 * @param result Result buffer 3196 * @param i Initial offset into result buffer 3197 * @return Offset into result buffer after writing chars 3198 */ 3199 int handleNegative(char[] result, int i) { 3200 return i; 3201 } 3202 } 3203} 3204 3205// End Format.java