From 0ff5251e345ccb4271c5d80e7e21e9c9cce154d1 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Tue, 14 Oct 2014 12:38:06 -0400 Subject: [PATCH] Initial commit with no changes to jdk classes --- .gitignore | 3 + LICENSE | 347 ++ pom.xml | 102 + readme.md | 37 + .../text/CharacterIteratorFieldDelegate.java | 124 + .../moparisthebest/text/DecimalFormat.java | 4190 +++++++++++++++++ .../com/moparisthebest/text/DigitList.java | 715 +++ .../moparisthebest/text/FieldDelegate.java | 83 + .../moparisthebest/text/OldDecimalFormat.java | 65 + 9 files changed, 5666 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 pom.xml create mode 100644 readme.md create mode 100644 src/main/java/com/moparisthebest/text/CharacterIteratorFieldDelegate.java create mode 100644 src/main/java/com/moparisthebest/text/DecimalFormat.java create mode 100644 src/main/java/com/moparisthebest/text/DigitList.java create mode 100644 src/main/java/com/moparisthebest/text/FieldDelegate.java create mode 100644 src/main/java/com/moparisthebest/text/OldDecimalFormat.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9280055 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +**.iml +target/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b40a0f4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,347 @@ +The GNU General Public License (GPL) + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software is +covered by the GNU Library General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for this service if you wish), +that you receive source code or can get it if you want it, that you can change +the software or use pieces of it in new free programs; and that you know you +can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny +you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must +make sure that they, too, receive or can get the source code. And you must +show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients to +know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program proprietary. +To prevent this, we have made it clear that any patent must be licensed for +everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms of +this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is included +without limitation in the term "modification".) Each licensee is addressed as +"you". + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made by +running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as +you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may +at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus +forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) You must cause the modified files to carry prominent notices stating + that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in whole or + in part contains or is derived from the Program or any part thereof, to be + licensed as a whole at no charge to all third parties under the terms of + this License. + + c) If the modified program normally reads commands interactively when run, + you must cause it, when started running for such interactive use in the + most ordinary way, to print or display an announcement including an + appropriate copyright notice and a notice that there is no warranty (or + else, saying that you provide a warranty) and that users may redistribute + the program under these conditions, and telling the user how to view a copy + of this License. (Exception: if the Program itself is interactive but does + not normally print such an announcement, your work based on the Program is + not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, and +its terms, do not apply to those sections when you distribute them as separate +works. But when you distribute the same sections as part of a whole which is a +work based on the Program, the distribution of the whole must be on the terms +of this License, whose permissions for other licensees extend to the entire +whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise the +right to control the distribution of derivative or collective works based on +the Program. + +In addition, mere aggregation of another work not based on the Program with the +Program (or with a work based on the Program) on a volume of a storage or +distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 and +2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable source + code, which must be distributed under the terms of Sections 1 and 2 above + on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three years, to + give any third party, for a charge no more than your cost of physically + performing source distribution, a complete machine-readable copy of the + corresponding source code, to be distributed under the terms of Sections 1 + and 2 above on a medium customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer to + distribute corresponding source code. (This alternative is allowed only + for noncommercial distribution and only if you received the program in + object code or executable form with such an offer, in accord with + Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code +distributed need not include anything that is normally distributed (in either +source or binary form) with the major components (compiler, kernel, and so on) +of the operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the source +code from the same place counts as distribution of the source code, even though +third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, or +rights, from you under this License will not have their licenses terminated so +long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Program +or its derivative works. These actions are prohibited by law if you do not +accept this License. Therefore, by modifying or distributing the Program (or +any work based on the Program), you indicate your acceptance of this License to +do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor to +copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of the +rights granted herein. You are not responsible for enforcing compliance by +third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose that +choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In +such case, this License incorporates the limitation as if written in the body +of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any later +version", you have the option of following the terms and conditions either of +that version or of any later version published by the Free Software Foundation. +If the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status of +all derivatives of our free software and of promoting the sharing and reuse of +software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE +PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, +YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA +BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER +OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively convey the exclusion +of warranty; and each file should have at least the "copyright" line and a +pointer to where the full notice is found. + + One line to give the program's name and a brief idea of what it does. + + Copyright (C) + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2 of the License, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., 59 + Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when it +starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes + with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free + software, and you are welcome to redistribute it under certain conditions; + type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may be +called something other than 'show w' and 'show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + 'Gnomovision' (which makes passes at compilers) written by James Hacker. + + signature of Ty Coon, 1 April 1989 + + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General Public +License instead of this License. + + +"CLASSPATH" EXCEPTION TO THE GPL + +Certain source files distributed by Oracle America and/or its affiliates are +subject to the following clarification and special exception to the GPL, but +only where Oracle has expressly included in the particular source file's header +the words "Oracle designates this particular file as subject to the "Classpath" +exception as provided by Oracle in the LICENSE file that accompanied this code." + + Linking this library statically or dynamically with other modules is making + a combined work based on this library. Thus, the terms and conditions of + the GNU General Public License cover the whole combination. + + As a special exception, the copyright holders of this library give you + permission to link this library with independent modules to produce an + executable, regardless of the license terms of these independent modules, + and to copy and distribute the resulting executable under terms of your + choice, provided that you also meet, for each linked independent module, + the terms and conditions of the license of that module. An independent + module is a module which is not derived from or based on this library. If + you modify this library, you may extend this exception to your version of + the library, but you are not obligated to do so. If you do not wish to do + so, delete this exception statement from your version. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..3e7d410 --- /dev/null +++ b/pom.xml @@ -0,0 +1,102 @@ + + + + + org.sonatype.oss + oss-parent + 9 + + 4.0.0 + com.moparisthebest.text + java7decimalformat + jar + 1.0-SNAPSHOT + ${project.artifactId} + + This project takes GPLv2 code from jdk7 and jdk8 to create a class that extends DecimalFormat, but retains the + old rounding behavior of Java 6 and 7. + + https://github.com/moparisthebest/java7decimalformat + + moparisthebest.com + http://www.moparisthebest.com + + + + moparisthebest + Travis Burtrum + admin@moparisthebest.com + http://www.moparisthebest.com/ + + + + scm:git:https://github.com/moparisthebest/java7decimalformat.git + scm:git:https://github.com/moparisthebest/java7decimalformat.git + https://github.com/moparisthebest/java7decimalformat + + + + GNU General Public License, Version 2 + https://www.gnu.org/licenses/old-licenses/gpl-2.0.html + + + + true + UTF-8 + false + true + + + ${project.artifactId} + + + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + true + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.2 + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + run-tests + + + maven.test.skip + false + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..49605a3 --- /dev/null +++ b/readme.md @@ -0,0 +1,37 @@ +Java 7 DecimalFormat +-------------------- + +If you upgrade to Java 8 you may be bit by [this bug fix](https://bugs.openjdk.java.net/browse/JDK-7131459). A temporary JVM-wide fix that works is to copy java/text/DigitList.class from Java 7's rt.jar to Java 8's rt.jar, but who wants to run that way forever? + +This project takes GPLv2 code from jdk7 and jdk8 to create a class that extends DecimalFormat, but retains the old rounding behavior of Java 6 and 7. + +[This](http://hg.openjdk.java.net/jdk8/jdk8/jdk/rev/bc1f16f5566f) is the offending bug fix we are trying to revert, it only changes DigitList.java, but since it's package private and not an interface, there is no way to just change it's usages in only *some* instances of DecimalFormat, even with reflection. I took [DecimalFormat.java and some classes it depends on from jdk8](http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/text) and [DigitList.java from jdk7](http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/classes/java/text), which you will find unchanged in the first commit. The changes I had to make to get everything to work are included in the second and subsequent commits. + +To use this, replace your java.text.DecimalFormat usages with com.moparisthebest.text.DecimalFormat (or com.moparisthebest.text.OldDecimalFormat, they are the same). These classes are instances of java.text.DecimalFormat so only the constructors really need changed. com.moparisthebest.text.OldDecimalFormat has a main method that allows you to test various calculations and their output using the 'correct' method of BigDecimal for decimal math, and the two Decimalformat implementations, here is some example output: + + $ /usr/lib/jvm/java-8-oracle/bin/java com.moparisthebest.text.OldDecimalFormat 50 0.0259 2 + 50 * 0.0259 with precision of 2: + java.math.BigDecimal: 1.30 + com.moparisthebest.text.DecimalFormat: 1.30 + java.text.DecimalFormat: 1.29 + +License +------- + +By necessity, this must inherit the license Sun/Oracle provided the classes under, which is GPLv2 only, the headers in the classes are unchanged. A copy of the full license is available in LICENSE. + + This code is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License version 2 only, as + published by the Free Software Foundation. Oracle designates this + particular file as subject to the "Classpath" exception as provided + by Oracle in the LICENSE file that accompanied this code. + + This code is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + version 2 for more details (a copy is included in the LICENSE file that + accompanied this code). + + You should have received a copy of the GNU General Public License version + 2 along with this work; if not, write to the Free Software Foundation, + Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/src/main/java/com/moparisthebest/text/CharacterIteratorFieldDelegate.java b/src/main/java/com/moparisthebest/text/CharacterIteratorFieldDelegate.java new file mode 100644 index 0000000..9e22ee7 --- /dev/null +++ b/src/main/java/com/moparisthebest/text/CharacterIteratorFieldDelegate.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.text; + +import java.util.ArrayList; + +/** + * CharacterIteratorFieldDelegate combines the notifications from a Format + * into a resulting AttributedCharacterIterator. The resulting + * AttributedCharacterIterator can be retrieved by way of + * the getIterator method. + * + */ +class CharacterIteratorFieldDelegate implements Format.FieldDelegate { + /** + * Array of AttributeStrings. Whenever formatted is invoked + * for a region > size, a new instance of AttributedString is added to + * attributedStrings. Subsequent invocations of formatted + * for existing regions result in invoking addAttribute on the existing + * AttributedStrings. + */ + private ArrayList attributedStrings; + /** + * Running count of the number of characters that have + * been encountered. + */ + private int size; + + + CharacterIteratorFieldDelegate() { + attributedStrings = new ArrayList<>(); + } + + public void formatted(Format.Field attr, Object value, int start, int end, + StringBuffer buffer) { + if (start != end) { + if (start < size) { + // Adjust attributes of existing runs + int index = size; + int asIndex = attributedStrings.size() - 1; + + while (start < index) { + AttributedString as = attributedStrings. + get(asIndex--); + int newIndex = index - as.length(); + int aStart = Math.max(0, start - newIndex); + + as.addAttribute(attr, value, aStart, Math.min( + end - start, as.length() - aStart) + + aStart); + index = newIndex; + } + } + if (size < start) { + // Pad attributes + attributedStrings.add(new AttributedString( + buffer.substring(size, start))); + size = start; + } + if (size < end) { + // Add new string + int aStart = Math.max(start, size); + AttributedString string = new AttributedString( + buffer.substring(aStart, end)); + + string.addAttribute(attr, value); + attributedStrings.add(string); + size = end; + } + } + } + + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer) { + formatted(attr, value, start, end, buffer); + } + + /** + * Returns an AttributedCharacterIterator that can be used + * to iterate over the resulting formatted String. + * + * @pararm string Result of formatting. + */ + public AttributedCharacterIterator getIterator(String string) { + // Add the last AttributedCharacterIterator if necessary + // assert(size <= string.length()); + if (string.length() > size) { + attributedStrings.add(new AttributedString( + string.substring(size))); + size = string.length(); + } + int iCount = attributedStrings.size(); + AttributedCharacterIterator iterators[] = new + AttributedCharacterIterator[iCount]; + + for (int counter = 0; counter < iCount; counter++) { + iterators[counter] = attributedStrings. + get(counter).getIterator(); + } + return new AttributedString(iterators).getIterator(); + } +} diff --git a/src/main/java/com/moparisthebest/text/DecimalFormat.java b/src/main/java/com/moparisthebest/text/DecimalFormat.java new file mode 100644 index 0000000..f89fd15 --- /dev/null +++ b/src/main/java/com/moparisthebest/text/DecimalFormat.java @@ -0,0 +1,4190 @@ +/* + * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.text.spi.NumberFormatProvider; +import java.util.ArrayList; +import java.util.Currency; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import sun.util.locale.provider.LocaleProviderAdapter; +import sun.util.locale.provider.ResourceBundleBasedAdapter; + +/** + * DecimalFormat is a concrete subclass of + * NumberFormat that formats decimal numbers. It has a variety of + * features designed to make it possible to parse and format numbers in any + * locale, including support for Western, Arabic, and Indic digits. It also + * supports different kinds of numbers, including integers (123), fixed-point + * numbers (123.4), scientific notation (1.23E4), percentages (12%), and + * currency amounts ($123). All of these can be localized. + * + *

To obtain a NumberFormat for a specific locale, including the + * default locale, call one of NumberFormat's factory methods, such + * as getInstance(). In general, do not call the + * DecimalFormat constructors directly, since the + * NumberFormat factory methods may return subclasses other than + * DecimalFormat. If you need to customize the format object, do + * something like this: + * + *

+ * NumberFormat f = NumberFormat.getInstance(loc);
+ * if (f instanceof DecimalFormat) {
+ *     ((DecimalFormat) f).setDecimalSeparatorAlwaysShown(true);
+ * }
+ * 
+ * + *

A DecimalFormat comprises a pattern and a set of + * symbols. The pattern may be set directly using + * applyPattern(), or indirectly using the API methods. The + * symbols are stored in a DecimalFormatSymbols object. When using + * the NumberFormat factory methods, the pattern and symbols are + * read from localized ResourceBundles. + * + *

Patterns

+ * + * DecimalFormat patterns have the following syntax: + *
+ * Pattern:
+ *         PositivePattern
+ *         PositivePattern ; NegativePattern
+ * PositivePattern:
+ *         Prefixopt Number Suffixopt
+ * NegativePattern:
+ *         Prefixopt Number Suffixopt
+ * Prefix:
+ *         any Unicode characters except \uFFFE, \uFFFF, and special characters
+ * Suffix:
+ *         any Unicode characters except \uFFFE, \uFFFF, and special characters
+ * Number:
+ *         Integer Exponentopt
+ *         Integer . Fraction Exponentopt
+ * Integer:
+ *         MinimumInteger
+ *         #
+ *         # Integer
+ *         # , Integer
+ * MinimumInteger:
+ *         0
+ *         0 MinimumInteger
+ *         0 , MinimumInteger
+ * Fraction:
+ *         MinimumFractionopt OptionalFractionopt
+ * MinimumFraction:
+ *         0 MinimumFractionopt
+ * OptionalFraction:
+ *         # OptionalFractionopt
+ * Exponent:
+ *         E MinimumExponent
+ * MinimumExponent:
+ *         0 MinimumExponentopt
+ * 
+ * + *

A DecimalFormat pattern contains a positive and negative + * subpattern, for example, "#,##0.00;(#,##0.00)". Each + * subpattern has a prefix, numeric part, and suffix. The negative subpattern + * is optional; if absent, then the positive subpattern prefixed with the + * localized minus sign ('-' in most locales) is used as the + * negative subpattern. That is, "0.00" alone is equivalent to + * "0.00;-0.00". If there is an explicit negative subpattern, it + * serves only to specify the negative prefix and suffix; the number of digits, + * minimal digits, and other characteristics are all the same as the positive + * pattern. That means that "#,##0.0#;(#)" produces precisely + * the same behavior as "#,##0.0#;(#,##0.0#)". + * + *

The prefixes, suffixes, and various symbols used for infinity, digits, + * thousands separators, decimal separators, etc. may be set to arbitrary + * values, and they will appear properly during formatting. However, care must + * be taken that the symbols and strings do not conflict, or parsing will be + * unreliable. For example, either the positive and negative prefixes or the + * suffixes must be distinct for DecimalFormat.parse() to be able + * to distinguish positive from negative values. (If they are identical, then + * DecimalFormat will behave as if no negative subpattern was + * specified.) Another example is that the decimal separator and thousands + * separator should be distinct characters, or parsing will be impossible. + * + *

The grouping separator is commonly used for thousands, but in some + * countries it separates ten-thousands. The grouping size is a constant number + * of digits between the grouping characters, such as 3 for 100,000,000 or 4 for + * 1,0000,0000. If you supply a pattern with multiple grouping characters, the + * interval between the last one and the end of the integer is the one that is + * used. So "#,##,###,####" == "######,####" == + * "##,####,####". + * + *

Special Pattern Characters

+ * + *

Many characters in a pattern are taken literally; they are matched during + * parsing and output unchanged during formatting. Special characters, on the + * other hand, stand for other characters, strings, or classes of characters. + * They must be quoted, unless noted otherwise, if they are to appear in the + * prefix or suffix as literals. + * + *

The characters listed here are used in non-localized patterns. Localized + * patterns use the corresponding characters taken from this formatter's + * DecimalFormatSymbols object instead, and these characters lose + * their special status. Two exceptions are the currency sign and quote, which + * are not localized. + * + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
Symbol + * Location + * Localized? + * Meaning + *
0 + * Number + * Yes + * Digit + *
# + * Number + * Yes + * Digit, zero shows as absent + *
. + * Number + * Yes + * Decimal separator or monetary decimal separator + *
- + * Number + * Yes + * Minus sign + *
, + * Number + * Yes + * Grouping separator + *
E + * Number + * Yes + * Separates mantissa and exponent in scientific notation. + * Need not be quoted in prefix or suffix. + *
; + * Subpattern boundary + * Yes + * Separates positive and negative subpatterns + *
% + * Prefix or suffix + * Yes + * Multiply by 100 and show as percentage + *
\u2030 + * Prefix or suffix + * Yes + * Multiply by 1000 and show as per mille value + *
¤ (\u00A4) + * Prefix or suffix + * No + * Currency sign, replaced by currency symbol. If + * doubled, replaced by international currency symbol. + * If present in a pattern, the monetary decimal separator + * is used instead of the decimal separator. + *
' + * Prefix or suffix + * No + * Used to quote special characters in a prefix or suffix, + * for example, "'#'#" formats 123 to + * "#123". To create a single quote + * itself, use two in a row: "# o''clock". + *
+ *
+ * + *

Scientific Notation

+ * + *

Numbers in scientific notation are expressed as the product of a mantissa + * and a power of ten, for example, 1234 can be expressed as 1.234 x 10^3. The + * mantissa is often in the range 1.0 ≤ x {@literal <} 10.0, but it need not + * be. + * DecimalFormat can be instructed to format and parse scientific + * notation only via a pattern; there is currently no factory method + * that creates a scientific notation format. In a pattern, the exponent + * character immediately followed by one or more digit characters indicates + * scientific notation. Example: "0.###E0" formats the number + * 1234 as "1.234E3". + * + *

    + *
  • The number of digit characters after the exponent character gives the + * minimum exponent digit count. There is no maximum. Negative exponents are + * formatted using the localized minus sign, not the prefix and suffix + * from the pattern. This allows patterns such as "0.###E0 m/s". + * + *
  • The minimum and maximum number of integer digits are interpreted + * together: + * + *
      + *
    • If the maximum number of integer digits is greater than their minimum number + * and greater than 1, it forces the exponent to be a multiple of the maximum + * number of integer digits, and the minimum number of integer digits to be + * interpreted as 1. The most common use of this is to generate + * engineering notation, in which the exponent is a multiple of three, + * e.g., "##0.#####E0". Using this pattern, the number 12345 + * formats to "12.345E3", and 123456 formats to + * "123.456E3". + * + *
    • Otherwise, the minimum number of integer digits is achieved by adjusting the + * exponent. Example: 0.00123 formatted with "00.###E0" yields + * "12.3E-4". + *
    + * + *
  • The number of significant digits in the mantissa is the sum of the + * minimum integer and maximum fraction digits, and is + * unaffected by the maximum integer digits. For example, 12345 formatted with + * "##0.##E0" is "12.3E3". To show all digits, set + * the significant digits count to zero. The number of significant digits + * does not affect parsing. + * + *
  • Exponential patterns may not contain grouping separators. + *
+ * + *

Rounding

+ * + * DecimalFormat provides rounding modes defined in + * {@link java.math.RoundingMode} for formatting. By default, it uses + * {@link java.math.RoundingMode#HALF_EVEN RoundingMode.HALF_EVEN}. + * + *

Digits

+ * + * For formatting, DecimalFormat uses the ten consecutive + * characters starting with the localized zero digit defined in the + * DecimalFormatSymbols object as digits. For parsing, these + * digits as well as all Unicode decimal digits, as defined by + * {@link Character#digit Character.digit}, are recognized. + * + *

Special Values

+ * + *

NaN is formatted as a string, which typically has a single character + * \uFFFD. This string is determined by the + * DecimalFormatSymbols object. This is the only value for which + * the prefixes and suffixes are not used. + * + *

Infinity is formatted as a string, which typically has a single character + * \u221E, with the positive or negative prefixes and suffixes + * applied. The infinity string is determined by the + * DecimalFormatSymbols object. + * + *

Negative zero ("-0") parses to + *

    + *
  • BigDecimal(0) if isParseBigDecimal() is + * true, + *
  • Long(0) if isParseBigDecimal() is false + * and isParseIntegerOnly() is true, + *
  • Double(-0.0) if both isParseBigDecimal() + * and isParseIntegerOnly() are false. + *
+ * + *

Synchronization

+ * + *

+ * Decimal formats are generally not synchronized. + * It is recommended to create separate format instances for each thread. + * If multiple threads access a format concurrently, it must be synchronized + * externally. + * + *

Example

+ * + *
{@code
+ * // Print out a number using the localized number, integer, currency,
+ * // and percent format for each locale
+ * Locale[] locales = NumberFormat.getAvailableLocales();
+ * double myNumber = -1234.56;
+ * NumberFormat form;
+ * for (int j = 0; j < 4; ++j) {
+ *     System.out.println("FORMAT");
+ *     for (int i = 0; i < locales.length; ++i) {
+ *         if (locales[i].getCountry().length() == 0) {
+ *            continue; // Skip language-only locales
+ *         }
+ *         System.out.print(locales[i].getDisplayName());
+ *         switch (j) {
+ *         case 0:
+ *             form = NumberFormat.getInstance(locales[i]); break;
+ *         case 1:
+ *             form = NumberFormat.getIntegerInstance(locales[i]); break;
+ *         case 2:
+ *             form = NumberFormat.getCurrencyInstance(locales[i]); break;
+ *         default:
+ *             form = NumberFormat.getPercentInstance(locales[i]); break;
+ *         }
+ *         if (form instanceof DecimalFormat) {
+ *             System.out.print(": " + ((DecimalFormat) form).toPattern());
+ *         }
+ *         System.out.print(" -> " + form.format(myNumber));
+ *         try {
+ *             System.out.println(" -> " + form.parse(form.format(myNumber)));
+ *         } catch (ParseException e) {}
+ *     }
+ * }
+ * }
+ * + * @see Java Tutorial + * @see NumberFormat + * @see DecimalFormatSymbols + * @see ParsePosition + * @author Mark Davis + * @author Alan Liu + */ +public class DecimalFormat extends NumberFormat { + + /** + * Creates a DecimalFormat using the default pattern and symbols + * for the default {@link java.util.Locale.Category#FORMAT FORMAT} locale. + * This is a convenient way to obtain a + * DecimalFormat when internationalization is not the main concern. + *

+ * To obtain standard formats for a given locale, use the factory methods + * on NumberFormat such as getNumberInstance. These factories will + * return the most appropriate sub-class of NumberFormat for a given + * locale. + * + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + */ + public DecimalFormat() { + // Get the pattern for the default locale. + Locale def = Locale.getDefault(Locale.Category.FORMAT); + LocaleProviderAdapter adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class, def); + if (!(adapter instanceof ResourceBundleBasedAdapter)) { + adapter = LocaleProviderAdapter.getResourceBundleBased(); + } + String[] all = adapter.getLocaleResources(def).getNumberPatterns(); + + // Always applyPattern after the symbols are set + this.symbols = DecimalFormatSymbols.getInstance(def); + applyPattern(all[0], false); + } + + + /** + * Creates a DecimalFormat using the given pattern and the symbols + * for the default {@link java.util.Locale.Category#FORMAT FORMAT} locale. + * This is a convenient way to obtain a + * DecimalFormat when internationalization is not the main concern. + *

+ * To obtain standard formats for a given locale, use the factory methods + * on NumberFormat such as getNumberInstance. These factories will + * return the most appropriate sub-class of NumberFormat for a given + * locale. + * + * @param pattern a non-localized pattern string. + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + */ + public DecimalFormat(String pattern) { + // Always applyPattern after the symbols are set + this.symbols = DecimalFormatSymbols.getInstance(Locale.getDefault(Locale.Category.FORMAT)); + applyPattern(pattern, false); + } + + + /** + * Creates a DecimalFormat using the given pattern and symbols. + * Use this constructor when you need to completely customize the + * behavior of the format. + *

+ * To obtain standard formats for a given + * locale, use the factory methods on NumberFormat such as + * getInstance or getCurrencyInstance. If you need only minor adjustments + * to a standard format, you can modify the format returned by + * a NumberFormat factory method. + * + * @param pattern a non-localized pattern string + * @param symbols the set of symbols to be used + * @exception NullPointerException if any of the given arguments is null + * @exception IllegalArgumentException if the given pattern is invalid + * @see java.text.NumberFormat#getInstance + * @see java.text.NumberFormat#getNumberInstance + * @see java.text.NumberFormat#getCurrencyInstance + * @see java.text.NumberFormat#getPercentInstance + * @see java.text.DecimalFormatSymbols + */ + public DecimalFormat (String pattern, DecimalFormatSymbols symbols) { + // Always applyPattern after the symbols are set + this.symbols = (DecimalFormatSymbols)symbols.clone(); + applyPattern(pattern, false); + } + + + // Overrides + /** + * Formats a number and appends the resulting text to the given string + * buffer. + * The number can be of any subclass of {@link java.lang.Number}. + *

+ * This implementation uses the maximum precision permitted. + * @param number the number to format + * @param toAppendTo the StringBuffer to which the formatted + * text is to be appended + * @param pos On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return the value passed in as toAppendTo + * @exception IllegalArgumentException if number is + * null or not an instance of Number. + * @exception NullPointerException if toAppendTo or + * pos is null + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + @Override + public final StringBuffer format(Object number, + StringBuffer toAppendTo, + FieldPosition pos) { + if (number instanceof Long || number instanceof Integer || + number instanceof Short || number instanceof Byte || + number instanceof AtomicInteger || + number instanceof AtomicLong || + (number instanceof BigInteger && + ((BigInteger)number).bitLength () < 64)) { + return format(((Number)number).longValue(), toAppendTo, pos); + } else if (number instanceof BigDecimal) { + return format((BigDecimal)number, toAppendTo, pos); + } else if (number instanceof BigInteger) { + return format((BigInteger)number, toAppendTo, pos); + } else if (number instanceof Number) { + return format(((Number)number).doubleValue(), toAppendTo, pos); + } else { + throw new IllegalArgumentException("Cannot format given Object as a Number"); + } + } + + /** + * Formats a double to produce a string. + * @param number The double to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + * @see java.text.FieldPosition + */ + @Override + public StringBuffer format(double number, StringBuffer result, + FieldPosition fieldPosition) { + // If fieldPosition is a DontCareFieldPosition instance we can + // try to go to fast-path code. + boolean tryFastPath = false; + if (fieldPosition == DontCareFieldPosition.INSTANCE) + tryFastPath = true; + else { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + } + + if (tryFastPath) { + String tempResult = fastFormat(number); + if (tempResult != null) { + result.append(tempResult); + return result; + } + } + + // if fast-path could not work, we fallback to standard code. + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Formats a double to produce a string. + * @param number The double to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + */ + private StringBuffer format(double number, StringBuffer result, + FieldDelegate delegate) { + if (Double.isNaN(number) || + (Double.isInfinite(number) && multiplier == 0)) { + int iFieldStart = result.length(); + result.append(symbols.getNaN()); + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + return result; + } + + /* Detecting whether a double is negative is easy with the exception of + * the value -0.0. This is a double which has a zero mantissa (and + * exponent), but a negative sign bit. It is semantically distinct from + * a zero with a positive sign bit, and this distinction is important + * to certain kinds of computations. However, it's a little tricky to + * detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you may + * ask, does it behave distinctly from +0.0? Well, 1/(-0.0) == + * -Infinity. Proper detection of -0.0 is needed to deal with the + * issues raised by bugs 4106658, 4106667, and 4147706. Liu 7/6/98. + */ + boolean isNegative = ((number < 0.0) || (number == 0.0 && 1/number < 0.0)) ^ (multiplier < 0); + + if (multiplier != 1) { + number *= multiplier; + } + + if (Double.isInfinite(number)) { + if (isNegative) { + append(result, negativePrefix, delegate, + getNegativePrefixFieldPositions(), Field.SIGN); + } else { + append(result, positivePrefix, delegate, + getPositivePrefixFieldPositions(), Field.SIGN); + } + + int iFieldStart = result.length(); + result.append(symbols.getInfinity()); + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + + if (isNegative) { + append(result, negativeSuffix, delegate, + getNegativeSuffixFieldPositions(), Field.SIGN); + } else { + append(result, positiveSuffix, delegate, + getPositiveSuffixFieldPositions(), Field.SIGN); + } + + return result; + } + + if (isNegative) { + number = -number; + } + + // at this point we are guaranteed a nonnegative finite number. + assert(number >= 0 && !Double.isInfinite(number)); + + synchronized(digitList) { + int maxIntDigits = super.getMaximumIntegerDigits(); + int minIntDigits = super.getMinimumIntegerDigits(); + int maxFraDigits = super.getMaximumFractionDigits(); + int minFraDigits = super.getMinimumFractionDigits(); + + digitList.set(isNegative, number, useExponentialNotation ? + maxIntDigits + maxFraDigits : maxFraDigits, + !useExponentialNotation); + return subformat(result, delegate, isNegative, false, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Format a long to produce a string. + * @param number The long to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + * @see java.text.FieldPosition + */ + @Override + public StringBuffer format(long number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Format a long to produce a string. + * @param number The long to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(long number, StringBuffer result, + FieldDelegate delegate) { + boolean isNegative = (number < 0); + if (isNegative) { + number = -number; + } + + // In general, long values always represent real finite numbers, so + // we don't have to check for +/- Infinity or NaN. However, there + // is one case we have to be careful of: The multiplier can push + // a number near MIN_VALUE or MAX_VALUE outside the legal range. We + // check for this before multiplying, and if it happens we use + // BigInteger instead. + boolean useBigInteger = false; + if (number < 0) { // This can only happen if number == Long.MIN_VALUE. + if (multiplier != 0) { + useBigInteger = true; + } + } else if (multiplier != 1 && multiplier != 0) { + long cutoff = Long.MAX_VALUE / multiplier; + if (cutoff < 0) { + cutoff = -cutoff; + } + useBigInteger = (number > cutoff); + } + + if (useBigInteger) { + if (isNegative) { + number = -number; + } + BigInteger bigIntegerValue = BigInteger.valueOf(number); + return format(bigIntegerValue, result, delegate, true); + } + + number *= multiplier; + if (number == 0) { + isNegative = false; + } else { + if (multiplier < 0) { + number = -number; + isNegative = !isNegative; + } + } + + synchronized(digitList) { + int maxIntDigits = super.getMaximumIntegerDigits(); + int minIntDigits = super.getMinimumIntegerDigits(); + int maxFraDigits = super.getMaximumFractionDigits(); + int minFraDigits = super.getMinimumFractionDigits(); + + digitList.set(isNegative, number, + useExponentialNotation ? maxIntDigits + maxFraDigits : 0); + + return subformat(result, delegate, isNegative, true, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Formats a BigDecimal to produce a string. + * @param number The BigDecimal to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigDecimal number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + return format(number, result, fieldPosition.getFieldDelegate()); + } + + /** + * Formats a BigDecimal to produce a string. + * @param number The BigDecimal to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return The formatted number string + */ + private StringBuffer format(BigDecimal number, StringBuffer result, + FieldDelegate delegate) { + if (multiplier != 1) { + number = number.multiply(getBigDecimalMultiplier()); + } + boolean isNegative = number.signum() == -1; + if (isNegative) { + number = number.negate(); + } + + synchronized(digitList) { + int maxIntDigits = getMaximumIntegerDigits(); + int minIntDigits = getMinimumIntegerDigits(); + int maxFraDigits = getMaximumFractionDigits(); + int minFraDigits = getMinimumFractionDigits(); + int maximumDigits = maxIntDigits + maxFraDigits; + + digitList.set(isNegative, number, useExponentialNotation ? + ((maximumDigits < 0) ? Integer.MAX_VALUE : maximumDigits) : + maxFraDigits, !useExponentialNotation); + + return subformat(result, delegate, isNegative, false, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Format a BigInteger to produce a string. + * @param number The BigInteger to format + * @param result where the text is to be appended + * @param fieldPosition On input: an alignment field, if desired. + * On output: the offsets of the alignment field. + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigInteger number, StringBuffer result, + FieldPosition fieldPosition) { + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + + return format(number, result, fieldPosition.getFieldDelegate(), false); + } + + /** + * Format a BigInteger to produce a string. + * @param number The BigInteger to format + * @param result where the text is to be appended + * @param delegate notified of locations of sub fields + * @return The formatted number string + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @see java.text.FieldPosition + */ + private StringBuffer format(BigInteger number, StringBuffer result, + FieldDelegate delegate, boolean formatLong) { + if (multiplier != 1) { + number = number.multiply(getBigIntegerMultiplier()); + } + boolean isNegative = number.signum() == -1; + if (isNegative) { + number = number.negate(); + } + + synchronized(digitList) { + int maxIntDigits, minIntDigits, maxFraDigits, minFraDigits, maximumDigits; + if (formatLong) { + maxIntDigits = super.getMaximumIntegerDigits(); + minIntDigits = super.getMinimumIntegerDigits(); + maxFraDigits = super.getMaximumFractionDigits(); + minFraDigits = super.getMinimumFractionDigits(); + maximumDigits = maxIntDigits + maxFraDigits; + } else { + maxIntDigits = getMaximumIntegerDigits(); + minIntDigits = getMinimumIntegerDigits(); + maxFraDigits = getMaximumFractionDigits(); + minFraDigits = getMinimumFractionDigits(); + maximumDigits = maxIntDigits + maxFraDigits; + if (maximumDigits < 0) { + maximumDigits = Integer.MAX_VALUE; + } + } + + digitList.set(isNegative, number, + useExponentialNotation ? maximumDigits : 0); + + return subformat(result, delegate, isNegative, true, + maxIntDigits, minIntDigits, maxFraDigits, minFraDigits); + } + } + + /** + * Formats an Object producing an AttributedCharacterIterator. + * You can use the returned AttributedCharacterIterator + * to build the resulting String, as well as to determine information + * about the resulting String. + *

+ * Each attribute key of the AttributedCharacterIterator will be of type + * NumberFormat.Field, with the attribute value being the + * same as the attribute key. + * + * @exception NullPointerException if obj is null. + * @exception IllegalArgumentException when the Format cannot format the + * given object. + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @param obj The object to format + * @return AttributedCharacterIterator describing the formatted value. + * @since 1.4 + */ + @Override + public AttributedCharacterIterator formatToCharacterIterator(Object obj) { + CharacterIteratorFieldDelegate delegate = + new CharacterIteratorFieldDelegate(); + StringBuffer sb = new StringBuffer(); + + if (obj instanceof Double || obj instanceof Float) { + format(((Number)obj).doubleValue(), sb, delegate); + } else if (obj instanceof Long || obj instanceof Integer || + obj instanceof Short || obj instanceof Byte || + obj instanceof AtomicInteger || obj instanceof AtomicLong) { + format(((Number)obj).longValue(), sb, delegate); + } else if (obj instanceof BigDecimal) { + format((BigDecimal)obj, sb, delegate); + } else if (obj instanceof BigInteger) { + format((BigInteger)obj, sb, delegate, false); + } else if (obj == null) { + throw new NullPointerException( + "formatToCharacterIterator must be passed non-null object"); + } else { + throw new IllegalArgumentException( + "Cannot format given Object as a Number"); + } + return delegate.getIterator(sb.toString()); + } + + // ==== Begin fast-path formating logic for double ========================= + + /* Fast-path formatting will be used for format(double ...) methods iff a + * number of conditions are met (see checkAndSetFastPathStatus()): + * - Only if instance properties meet the right predefined conditions. + * - The abs value of the double to format is <= Integer.MAX_VALUE. + * + * The basic approach is to split the binary to decimal conversion of a + * double value into two phases: + * * The conversion of the integer portion of the double. + * * The conversion of the fractional portion of the double + * (limited to two or three digits). + * + * The isolation and conversion of the integer portion of the double is + * straightforward. The conversion of the fraction is more subtle and relies + * on some rounding properties of double to the decimal precisions in + * question. Using the terminology of BigDecimal, this fast-path algorithm + * is applied when a double value has a magnitude less than Integer.MAX_VALUE + * and rounding is to nearest even and the destination format has two or + * three digits of *scale* (digits after the decimal point). + * + * Under a rounding to nearest even policy, the returned result is a digit + * string of a number in the (in this case decimal) destination format + * closest to the exact numerical value of the (in this case binary) input + * value. If two destination format numbers are equally distant, the one + * with the last digit even is returned. To compute such a correctly rounded + * value, some information about digits beyond the smallest returned digit + * position needs to be consulted. + * + * In general, a guard digit, a round digit, and a sticky *bit* are needed + * beyond the returned digit position. If the discarded portion of the input + * is sufficiently large, the returned digit string is incremented. In round + * to nearest even, this threshold to increment occurs near the half-way + * point between digits. The sticky bit records if there are any remaining + * trailing digits of the exact input value in the new format; the sticky bit + * is consulted only in close to half-way rounding cases. + * + * Given the computation of the digit and bit values, rounding is then + * reduced to a table lookup problem. For decimal, the even/odd cases look + * like this: + * + * Last Round Sticky + * 6 5 0 => 6 // exactly halfway, return even digit. + * 6 5 1 => 7 // a little bit more than halfway, round up. + * 7 5 0 => 8 // exactly halfway, round up to even. + * 7 5 1 => 8 // a little bit more than halfway, round up. + * With analogous entries for other even and odd last-returned digits. + * + * However, decimal negative powers of 5 smaller than 0.5 are *not* exactly + * representable as binary fraction. In particular, 0.005 (the round limit + * for a two-digit scale) and 0.0005 (the round limit for a three-digit + * scale) are not representable. Therefore, for input values near these cases + * the sticky bit is known to be set which reduces the rounding logic to: + * + * Last Round Sticky + * 6 5 1 => 7 // a little bit more than halfway, round up. + * 7 5 1 => 8 // a little bit more than halfway, round up. + * + * In other words, if the round digit is 5, the sticky bit is known to be + * set. If the round digit is something other than 5, the sticky bit is not + * relevant. Therefore, some of the logic about whether or not to increment + * the destination *decimal* value can occur based on tests of *binary* + * computations of the binary input number. + */ + + /** + * Check validity of using fast-path for this instance. If fast-path is valid + * for this instance, sets fast-path state as true and initializes fast-path + * utility fields as needed. + * + * This method is supposed to be called rarely, otherwise that will break the + * fast-path performance. That means avoiding frequent changes of the + * properties of the instance, since for most properties, each time a change + * happens, a call to this method is needed at the next format call. + * + * FAST-PATH RULES: + * Similar to the default DecimalFormat instantiation case. + * More precisely: + * - HALF_EVEN rounding mode, + * - isGroupingUsed() is true, + * - groupingSize of 3, + * - multiplier is 1, + * - Decimal separator not mandatory, + * - No use of exponential notation, + * - minimumIntegerDigits is exactly 1 and maximumIntegerDigits at least 10 + * - For number of fractional digits, the exact values found in the default case: + * Currency : min = max = 2. + * Decimal : min = 0. max = 3. + * + */ + private void checkAndSetFastPathStatus() { + + boolean fastPathWasOn = isFastPath; + + if ((roundingMode == RoundingMode.HALF_EVEN) && + (isGroupingUsed()) && + (groupingSize == 3) && + (multiplier == 1) && + (!decimalSeparatorAlwaysShown) && + (!useExponentialNotation)) { + + // The fast-path algorithm is semi-hardcoded against + // minimumIntegerDigits and maximumIntegerDigits. + isFastPath = ((minimumIntegerDigits == 1) && + (maximumIntegerDigits >= 10)); + + // The fast-path algorithm is hardcoded against + // minimumFractionDigits and maximumFractionDigits. + if (isFastPath) { + if (isCurrencyFormat) { + if ((minimumFractionDigits != 2) || + (maximumFractionDigits != 2)) + isFastPath = false; + } else if ((minimumFractionDigits != 0) || + (maximumFractionDigits != 3)) + isFastPath = false; + } + } else + isFastPath = false; + + // Since some instance properties may have changed while still falling + // in the fast-path case, we need to reinitialize fastPathData anyway. + if (isFastPath) { + // We need to instantiate fastPathData if not already done. + if (fastPathData == null) + fastPathData = new FastPathData(); + + // Sets up the locale specific constants used when formatting. + // '0' is our default representation of zero. + fastPathData.zeroDelta = symbols.getZeroDigit() - '0'; + fastPathData.groupingChar = symbols.getGroupingSeparator(); + + // Sets up fractional constants related to currency/decimal pattern. + fastPathData.fractionalMaxIntBound = (isCurrencyFormat) ? 99 : 999; + fastPathData.fractionalScaleFactor = (isCurrencyFormat) ? 100.0d : 1000.0d; + + // Records the need for adding prefix or suffix + fastPathData.positiveAffixesRequired = + (positivePrefix.length() != 0) || (positiveSuffix.length() != 0); + fastPathData.negativeAffixesRequired = + (negativePrefix.length() != 0) || (negativeSuffix.length() != 0); + + // Creates a cached char container for result, with max possible size. + int maxNbIntegralDigits = 10; + int maxNbGroups = 3; + int containerSize = + Math.max(positivePrefix.length(), negativePrefix.length()) + + maxNbIntegralDigits + maxNbGroups + 1 + maximumFractionDigits + + Math.max(positiveSuffix.length(), negativeSuffix.length()); + + fastPathData.fastPathContainer = new char[containerSize]; + + // Sets up prefix and suffix char arrays constants. + fastPathData.charsPositiveSuffix = positiveSuffix.toCharArray(); + fastPathData.charsNegativeSuffix = negativeSuffix.toCharArray(); + fastPathData.charsPositivePrefix = positivePrefix.toCharArray(); + fastPathData.charsNegativePrefix = negativePrefix.toCharArray(); + + // Sets up fixed index positions for integral and fractional digits. + // Sets up decimal point in cached result container. + int longestPrefixLength = + Math.max(positivePrefix.length(), negativePrefix.length()); + int decimalPointIndex = + maxNbIntegralDigits + maxNbGroups + longestPrefixLength; + + fastPathData.integralLastIndex = decimalPointIndex - 1; + fastPathData.fractionalFirstIndex = decimalPointIndex + 1; + fastPathData.fastPathContainer[decimalPointIndex] = + isCurrencyFormat ? + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + + } else if (fastPathWasOn) { + // Previous state was fast-path and is no more. + // Resets cached array constants. + fastPathData.fastPathContainer = null; + fastPathData.charsPositiveSuffix = null; + fastPathData.charsNegativeSuffix = null; + fastPathData.charsPositivePrefix = null; + fastPathData.charsNegativePrefix = null; + } + + fastPathCheckNeeded = false; + } + + /** + * Returns true if rounding-up must be done on {@code scaledFractionalPartAsInt}, + * false otherwise. + * + * This is a utility method that takes correct half-even rounding decision on + * passed fractional value at the scaled decimal point (2 digits for currency + * case and 3 for decimal case), when the approximated fractional part after + * scaled decimal point is exactly 0.5d. This is done by means of exact + * calculations on the {@code fractionalPart} floating-point value. + * + * This method is supposed to be called by private {@code fastDoubleFormat} + * method only. + * + * The algorithms used for the exact calculations are : + * + * The FastTwoSum algorithm, from T.J.Dekker, described in the + * papers "A Floating-Point Technique for Extending the Available + * Precision" by Dekker, and in "Adaptive Precision Floating-Point + * Arithmetic and Fast Robust Geometric Predicates" from J.Shewchuk. + * + * A modified version of Sum2S cascaded summation described in + * "Accurate Sum and Dot Product" from Takeshi Ogita and All. As + * Ogita says in this paper this is an equivalent of the Kahan-Babuska's + * summation algorithm because we order the terms by magnitude before summing + * them. For this reason we can use the FastTwoSum algorithm rather + * than the more expensive Knuth's TwoSum. + * + * We do this to avoid a more expensive exact "TwoProduct" algorithm, + * like those described in Shewchuk's paper above. See comments in the code + * below. + * + * @param fractionalPart The fractional value on which we take rounding + * decision. + * @param scaledFractionalPartAsInt The integral part of the scaled + * fractional value. + * + * @return the decision that must be taken regarding half-even rounding. + */ + private boolean exactRoundUp(double fractionalPart, + int scaledFractionalPartAsInt) { + + /* exactRoundUp() method is called by fastDoubleFormat() only. + * The precondition expected to be verified by the passed parameters is : + * scaledFractionalPartAsInt == + * (int) (fractionalPart * fastPathData.fractionalScaleFactor). + * This is ensured by fastDoubleFormat() code. + */ + + /* We first calculate roundoff error made by fastDoubleFormat() on + * the scaled fractional part. We do this with exact calculation on the + * passed fractionalPart. Rounding decision will then be taken from roundoff. + */ + + /* ---- TwoProduct(fractionalPart, scale factor (i.e. 1000.0d or 100.0d)). + * + * The below is an optimized exact "TwoProduct" calculation of passed + * fractional part with scale factor, using Ogita's Sum2S cascaded + * summation adapted as Kahan-Babuska equivalent by using FastTwoSum + * (much faster) rather than Knuth's TwoSum. + * + * We can do this because we order the summation from smallest to + * greatest, so that FastTwoSum can be used without any additional error. + * + * The "TwoProduct" exact calculation needs 17 flops. We replace this by + * a cascaded summation of FastTwoSum calculations, each involving an + * exact multiply by a power of 2. + * + * Doing so saves overall 4 multiplications and 1 addition compared to + * using traditional "TwoProduct". + * + * The scale factor is either 100 (currency case) or 1000 (decimal case). + * - when 1000, we replace it by (1024 - 16 - 8) = 1000. + * - when 100, we replace it by (128 - 32 + 4) = 100. + * Every multiplication by a power of 2 (1024, 128, 32, 16, 8, 4) is exact. + * + */ + double approxMax; // Will always be positive. + double approxMedium; // Will always be negative. + double approxMin; + + double fastTwoSumApproximation = 0.0d; + double fastTwoSumRoundOff = 0.0d; + double bVirtual = 0.0d; + + if (isCurrencyFormat) { + // Scale is 100 = 128 - 32 + 4. + // Multiply by 2**n is a shift. No roundoff. No error. + approxMax = fractionalPart * 128.00d; + approxMedium = - (fractionalPart * 32.00d); + approxMin = fractionalPart * 4.00d; + } else { + // Scale is 1000 = 1024 - 16 - 8. + // Multiply by 2**n is a shift. No roundoff. No error. + approxMax = fractionalPart * 1024.00d; + approxMedium = - (fractionalPart * 16.00d); + approxMin = - (fractionalPart * 8.00d); + } + + // Shewchuk/Dekker's FastTwoSum(approxMedium, approxMin). + assert(-approxMedium >= Math.abs(approxMin)); + fastTwoSumApproximation = approxMedium + approxMin; + bVirtual = fastTwoSumApproximation - approxMedium; + fastTwoSumRoundOff = approxMin - bVirtual; + double approxS1 = fastTwoSumApproximation; + double roundoffS1 = fastTwoSumRoundOff; + + // Shewchuk/Dekker's FastTwoSum(approxMax, approxS1); + assert(approxMax >= Math.abs(approxS1)); + fastTwoSumApproximation = approxMax + approxS1; + bVirtual = fastTwoSumApproximation - approxMax; + fastTwoSumRoundOff = approxS1 - bVirtual; + double roundoff1000 = fastTwoSumRoundOff; + double approx1000 = fastTwoSumApproximation; + double roundoffTotal = roundoffS1 + roundoff1000; + + // Shewchuk/Dekker's FastTwoSum(approx1000, roundoffTotal); + assert(approx1000 >= Math.abs(roundoffTotal)); + fastTwoSumApproximation = approx1000 + roundoffTotal; + bVirtual = fastTwoSumApproximation - approx1000; + + // Now we have got the roundoff for the scaled fractional + double scaledFractionalRoundoff = roundoffTotal - bVirtual; + + // ---- TwoProduct(fractionalPart, scale (i.e. 1000.0d or 100.0d)) end. + + /* ---- Taking the rounding decision + * + * We take rounding decision based on roundoff and half-even rounding + * rule. + * + * The above TwoProduct gives us the exact roundoff on the approximated + * scaled fractional, and we know that this approximation is exactly + * 0.5d, since that has already been tested by the caller + * (fastDoubleFormat). + * + * Decision comes first from the sign of the calculated exact roundoff. + * - Since being exact roundoff, it cannot be positive with a scaled + * fractional less than 0.5d, as well as negative with a scaled + * fractional greater than 0.5d. That leaves us with following 3 cases. + * - positive, thus scaled fractional == 0.500....0fff ==> round-up. + * - negative, thus scaled fractional == 0.499....9fff ==> don't round-up. + * - is zero, thus scaled fractioanl == 0.5 ==> half-even rounding applies : + * we round-up only if the integral part of the scaled fractional is odd. + * + */ + if (scaledFractionalRoundoff > 0.0) { + return true; + } else if (scaledFractionalRoundoff < 0.0) { + return false; + } else if ((scaledFractionalPartAsInt & 1) != 0) { + return true; + } + + return false; + + // ---- Taking the rounding decision end + } + + /** + * Collects integral digits from passed {@code number}, while setting + * grouping chars as needed. Updates {@code firstUsedIndex} accordingly. + * + * Loops downward starting from {@code backwardIndex} position (inclusive). + * + * @param number The int value from which we collect digits. + * @param digitsBuffer The char array container where digits and grouping chars + * are stored. + * @param backwardIndex the position from which we start storing digits in + * digitsBuffer. + * + */ + private void collectIntegralDigits(int number, + char[] digitsBuffer, + int backwardIndex) { + int index = backwardIndex; + int q; + int r; + while (number > 999) { + // Generates 3 digits per iteration. + q = number / 1000; + r = number - (q << 10) + (q << 4) + (q << 3); // -1024 +16 +8 = 1000. + number = q; + + digitsBuffer[index--] = DigitArrays.DigitOnes1000[r]; + digitsBuffer[index--] = DigitArrays.DigitTens1000[r]; + digitsBuffer[index--] = DigitArrays.DigitHundreds1000[r]; + digitsBuffer[index--] = fastPathData.groupingChar; + } + + // Collects last 3 or less digits. + digitsBuffer[index] = DigitArrays.DigitOnes1000[number]; + if (number > 9) { + digitsBuffer[--index] = DigitArrays.DigitTens1000[number]; + if (number > 99) + digitsBuffer[--index] = DigitArrays.DigitHundreds1000[number]; + } + + fastPathData.firstUsedIndex = index; + } + + /** + * Collects the 2 (currency) or 3 (decimal) fractional digits from passed + * {@code number}, starting at {@code startIndex} position + * inclusive. There is no punctuation to set here (no grouping chars). + * Updates {@code fastPathData.lastFreeIndex} accordingly. + * + * + * @param number The int value from which we collect digits. + * @param digitsBuffer The char array container where digits are stored. + * @param startIndex the position from which we start storing digits in + * digitsBuffer. + * + */ + private void collectFractionalDigits(int number, + char[] digitsBuffer, + int startIndex) { + int index = startIndex; + + char digitOnes = DigitArrays.DigitOnes1000[number]; + char digitTens = DigitArrays.DigitTens1000[number]; + + if (isCurrencyFormat) { + // Currency case. Always collects fractional digits. + digitsBuffer[index++] = digitTens; + digitsBuffer[index++] = digitOnes; + } else if (number != 0) { + // Decimal case. Hundreds will always be collected + digitsBuffer[index++] = DigitArrays.DigitHundreds1000[number]; + + // Ending zeros won't be collected. + if (digitOnes != '0') { + digitsBuffer[index++] = digitTens; + digitsBuffer[index++] = digitOnes; + } else if (digitTens != '0') + digitsBuffer[index++] = digitTens; + + } else + // This is decimal pattern and fractional part is zero. + // We must remove decimal point from result. + index--; + + fastPathData.lastFreeIndex = index; + } + + /** + * Internal utility. + * Adds the passed {@code prefix} and {@code suffix} to {@code container}. + * + * @param container Char array container which to prepend/append the + * prefix/suffix. + * @param prefix Char sequence to prepend as a prefix. + * @param suffix Char sequence to append as a suffix. + * + */ + // private void addAffixes(boolean isNegative, char[] container) { + private void addAffixes(char[] container, char[] prefix, char[] suffix) { + + // We add affixes only if needed (affix length > 0). + int pl = prefix.length; + int sl = suffix.length; + if (pl != 0) prependPrefix(prefix, pl, container); + if (sl != 0) appendSuffix(suffix, sl, container); + + } + + /** + * Prepends the passed {@code prefix} chars to given result + * {@code container}. Updates {@code fastPathData.firstUsedIndex} + * accordingly. + * + * @param prefix The prefix characters to prepend to result. + * @param len The number of chars to prepend. + * @param container Char array container which to prepend the prefix + */ + private void prependPrefix(char[] prefix, + int len, + char[] container) { + + fastPathData.firstUsedIndex -= len; + int startIndex = fastPathData.firstUsedIndex; + + // If prefix to prepend is only 1 char long, just assigns this char. + // If prefix is less or equal 4, we use a dedicated algorithm that + // has shown to run faster than System.arraycopy. + // If more than 4, we use System.arraycopy. + if (len == 1) + container[startIndex] = prefix[0]; + else if (len <= 4) { + int dstLower = startIndex; + int dstUpper = dstLower + len - 1; + int srcUpper = len - 1; + container[dstLower] = prefix[0]; + container[dstUpper] = prefix[srcUpper]; + + if (len > 2) + container[++dstLower] = prefix[1]; + if (len == 4) + container[--dstUpper] = prefix[2]; + } else + System.arraycopy(prefix, 0, container, startIndex, len); + } + + /** + * Appends the passed {@code suffix} chars to given result + * {@code container}. Updates {@code fastPathData.lastFreeIndex} + * accordingly. + * + * @param suffix The suffix characters to append to result. + * @param len The number of chars to append. + * @param container Char array container which to append the suffix + */ + private void appendSuffix(char[] suffix, + int len, + char[] container) { + + int startIndex = fastPathData.lastFreeIndex; + + // If suffix to append is only 1 char long, just assigns this char. + // If suffix is less or equal 4, we use a dedicated algorithm that + // has shown to run faster than System.arraycopy. + // If more than 4, we use System.arraycopy. + if (len == 1) + container[startIndex] = suffix[0]; + else if (len <= 4) { + int dstLower = startIndex; + int dstUpper = dstLower + len - 1; + int srcUpper = len - 1; + container[dstLower] = suffix[0]; + container[dstUpper] = suffix[srcUpper]; + + if (len > 2) + container[++dstLower] = suffix[1]; + if (len == 4) + container[--dstUpper] = suffix[2]; + } else + System.arraycopy(suffix, 0, container, startIndex, len); + + fastPathData.lastFreeIndex += len; + } + + /** + * Converts digit chars from {@code digitsBuffer} to current locale. + * + * Must be called before adding affixes since we refer to + * {@code fastPathData.firstUsedIndex} and {@code fastPathData.lastFreeIndex}, + * and do not support affixes (for speed reason). + * + * We loop backward starting from last used index in {@code fastPathData}. + * + * @param digitsBuffer The char array container where the digits are stored. + */ + private void localizeDigits(char[] digitsBuffer) { + + // We will localize only the digits, using the groupingSize, + // and taking into account fractional part. + + // First take into account fractional part. + int digitsCounter = + fastPathData.lastFreeIndex - fastPathData.fractionalFirstIndex; + + // The case when there is no fractional digits. + if (digitsCounter < 0) + digitsCounter = groupingSize; + + // Only the digits remains to localize. + for (int cursor = fastPathData.lastFreeIndex - 1; + cursor >= fastPathData.firstUsedIndex; + cursor--) { + if (digitsCounter != 0) { + // This is a digit char, we must localize it. + digitsBuffer[cursor] += fastPathData.zeroDelta; + digitsCounter--; + } else { + // Decimal separator or grouping char. Reinit counter only. + digitsCounter = groupingSize; + } + } + } + + /** + * This is the main entry point for the fast-path format algorithm. + * + * At this point we are sure to be in the expected conditions to run it. + * This algorithm builds the formatted result and puts it in the dedicated + * {@code fastPathData.fastPathContainer}. + * + * @param d the double value to be formatted. + * @param negative Flag precising if {@code d} is negative. + */ + private void fastDoubleFormat(double d, + boolean negative) { + + char[] container = fastPathData.fastPathContainer; + + /* + * The principle of the algorithm is to : + * - Break the passed double into its integral and fractional parts + * converted into integers. + * - Then decide if rounding up must be applied or not by following + * the half-even rounding rule, first using approximated scaled + * fractional part. + * - For the difficult cases (approximated scaled fractional part + * being exactly 0.5d), we refine the rounding decision by calling + * exactRoundUp utility method that both calculates the exact roundoff + * on the approximation and takes correct rounding decision. + * - We round-up the fractional part if needed, possibly propagating the + * rounding to integral part if we meet a "all-nine" case for the + * scaled fractional part. + * - We then collect digits from the resulting integral and fractional + * parts, also setting the required grouping chars on the fly. + * - Then we localize the collected digits if needed, and + * - Finally prepend/append prefix/suffix if any is needed. + */ + + // Exact integral part of d. + int integralPartAsInt = (int) d; + + // Exact fractional part of d (since we subtract it's integral part). + double exactFractionalPart = d - (double) integralPartAsInt; + + // Approximated scaled fractional part of d (due to multiplication). + double scaledFractional = + exactFractionalPart * fastPathData.fractionalScaleFactor; + + // Exact integral part of scaled fractional above. + int fractionalPartAsInt = (int) scaledFractional; + + // Exact fractional part of scaled fractional above. + scaledFractional = scaledFractional - (double) fractionalPartAsInt; + + // Only when scaledFractional is exactly 0.5d do we have to do exact + // calculations and take fine-grained rounding decision, since + // approximated results above may lead to incorrect decision. + // Otherwise comparing against 0.5d (strictly greater or less) is ok. + boolean roundItUp = false; + if (scaledFractional >= 0.5d) { + if (scaledFractional == 0.5d) + // Rounding need fine-grained decision. + roundItUp = exactRoundUp(exactFractionalPart, fractionalPartAsInt); + else + roundItUp = true; + + if (roundItUp) { + // Rounds up both fractional part (and also integral if needed). + if (fractionalPartAsInt < fastPathData.fractionalMaxIntBound) { + fractionalPartAsInt++; + } else { + // Propagates rounding to integral part since "all nines" case. + fractionalPartAsInt = 0; + integralPartAsInt++; + } + } + } + + // Collecting digits. + collectFractionalDigits(fractionalPartAsInt, container, + fastPathData.fractionalFirstIndex); + collectIntegralDigits(integralPartAsInt, container, + fastPathData.integralLastIndex); + + // Localizing digits. + if (fastPathData.zeroDelta != 0) + localizeDigits(container); + + // Adding prefix and suffix. + if (negative) { + if (fastPathData.negativeAffixesRequired) + addAffixes(container, + fastPathData.charsNegativePrefix, + fastPathData.charsNegativeSuffix); + } else if (fastPathData.positiveAffixesRequired) + addAffixes(container, + fastPathData.charsPositivePrefix, + fastPathData.charsPositiveSuffix); + } + + /** + * A fast-path shortcut of format(double) to be called by NumberFormat, or by + * format(double, ...) public methods. + * + * If instance can be applied fast-path and passed double is not NaN or + * Infinity, is in the integer range, we call {@code fastDoubleFormat} + * after changing {@code d} to its positive value if necessary. + * + * Otherwise returns null by convention since fast-path can't be exercized. + * + * @param d The double value to be formatted + * + * @return the formatted result for {@code d} as a string. + */ + String fastFormat(double d) { + // (Re-)Evaluates fast-path status if needed. + if (fastPathCheckNeeded) + checkAndSetFastPathStatus(); + + if (!isFastPath ) + // DecimalFormat instance is not in a fast-path state. + return null; + + if (!Double.isFinite(d)) + // Should not use fast-path for Infinity and NaN. + return null; + + // Extracts and records sign of double value, possibly changing it + // to a positive one, before calling fastDoubleFormat(). + boolean negative = false; + if (d < 0.0d) { + negative = true; + d = -d; + } else if (d == 0.0d) { + negative = (Math.copySign(1.0d, d) == -1.0d); + d = +0.0d; + } + + if (d > MAX_INT_AS_DOUBLE) + // Filters out values that are outside expected fast-path range + return null; + else + fastDoubleFormat(d, negative); + + // Returns a new string from updated fastPathContainer. + return new String(fastPathData.fastPathContainer, + fastPathData.firstUsedIndex, + fastPathData.lastFreeIndex - fastPathData.firstUsedIndex); + + } + + // ======== End fast-path formating logic for double ========================= + + /** + * Complete the formatting of a finite number. On entry, the digitList must + * be filled in with the correct digits. + */ + private StringBuffer subformat(StringBuffer result, FieldDelegate delegate, + boolean isNegative, boolean isInteger, + int maxIntDigits, int minIntDigits, + int maxFraDigits, int minFraDigits) { + // NOTE: This isn't required anymore because DigitList takes care of this. + // + // // The negative of the exponent represents the number of leading + // // zeros between the decimal and the first non-zero digit, for + // // a value < 0.1 (e.g., for 0.00123, -fExponent == 2). If this + // // is more than the maximum fraction digits, then we have an underflow + // // for the printed representation. We recognize this here and set + // // the DigitList representation to zero in this situation. + // + // if (-digitList.decimalAt >= getMaximumFractionDigits()) + // { + // digitList.count = 0; + // } + + char zero = symbols.getZeroDigit(); + int zeroDelta = zero - '0'; // '0' is the DigitList representation of zero + char grouping = symbols.getGroupingSeparator(); + char decimal = isCurrencyFormat ? + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + + /* Per bug 4147706, DecimalFormat must respect the sign of numbers which + * format as zero. This allows sensible computations and preserves + * relations such as signum(1/x) = signum(x), where x is +Infinity or + * -Infinity. Prior to this fix, we always formatted zero values as if + * they were positive. Liu 7/6/98. + */ + if (digitList.isZero()) { + digitList.decimalAt = 0; // Normalize + } + + if (isNegative) { + append(result, negativePrefix, delegate, + getNegativePrefixFieldPositions(), Field.SIGN); + } else { + append(result, positivePrefix, delegate, + getPositivePrefixFieldPositions(), Field.SIGN); + } + + if (useExponentialNotation) { + int iFieldStart = result.length(); + int iFieldEnd = -1; + int fFieldStart = -1; + + // Minimum integer digits are handled in exponential format by + // adjusting the exponent. For example, 0.01234 with 3 minimum + // integer digits is "123.4E-4". + + // Maximum integer digits are interpreted as indicating the + // repeating range. This is useful for engineering notation, in + // which the exponent is restricted to a multiple of 3. For + // example, 0.01234 with 3 maximum integer digits is "12.34e-3". + // If maximum integer digits are > 1 and are larger than + // minimum integer digits, then minimum integer digits are + // ignored. + int exponent = digitList.decimalAt; + int repeat = maxIntDigits; + int minimumIntegerDigits = minIntDigits; + if (repeat > 1 && repeat > minIntDigits) { + // A repeating range is defined; adjust to it as follows. + // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; + // -3,-4,-5=>-6, etc. This takes into account that the + // exponent we have here is off by one from what we expect; + // it is for the format 0.MMMMMx10^n. + if (exponent >= 1) { + exponent = ((exponent - 1) / repeat) * repeat; + } else { + // integer division rounds towards 0 + exponent = ((exponent - repeat) / repeat) * repeat; + } + minimumIntegerDigits = 1; + } else { + // No repeating range is defined; use minimum integer digits. + exponent -= minimumIntegerDigits; + } + + // We now output a minimum number of digits, and more if there + // are more digits, up to the maximum number of digits. We + // place the decimal point after the "integer" digits, which + // are the first (decimalAt - exponent) digits. + int minimumDigits = minIntDigits + minFraDigits; + if (minimumDigits < 0) { // overflow? + minimumDigits = Integer.MAX_VALUE; + } + + // The number of integer digits is handled specially if the number + // is zero, since then there may be no digits. + int integerDigits = digitList.isZero() ? minimumIntegerDigits : + digitList.decimalAt - exponent; + if (minimumDigits < integerDigits) { + minimumDigits = integerDigits; + } + int totalDigits = digitList.count; + if (minimumDigits > totalDigits) { + totalDigits = minimumDigits; + } + boolean addedDecimalSeparator = false; + + for (int i=0; i 0 && count < digitList.decimalAt) { + count = digitList.decimalAt; + } + + // Handle the case where getMaximumIntegerDigits() is smaller + // than the real number of integer digits. If this is so, we + // output the least significant max integer digits. For example, + // the value 1997 printed with 2 max integer digits is just "97". + if (count > maxIntDigits) { + count = maxIntDigits; + digitIndex = digitList.decimalAt - count; + } + + int sizeBeforeIntegerPart = result.length(); + for (int i=count-1; i>=0; --i) { + if (i < digitList.decimalAt && digitIndex < digitList.count) { + // Output a real digit + result.append((char)(digitList.digits[digitIndex++] + zeroDelta)); + } else { + // Output a leading zero + result.append(zero); + } + + // Output grouping separator if necessary. Don't output a + // grouping separator if i==0 though; that's at the end of + // the integer part. + if (isGroupingUsed() && i>0 && (groupingSize != 0) && + (i % groupingSize == 0)) { + int gStart = result.length(); + result.append(grouping); + delegate.formatted(Field.GROUPING_SEPARATOR, + Field.GROUPING_SEPARATOR, gStart, + result.length(), result); + } + } + + // Determine whether or not there are any printable fractional + // digits. If we've used up the digits we know there aren't. + boolean fractionPresent = (minFraDigits > 0) || + (!isInteger && digitIndex < digitList.count); + + // If there is no fraction present, and we haven't printed any + // integer digits, then print a zero. Otherwise we won't print + // _any_ digits, and we won't be able to parse this string. + if (!fractionPresent && result.length() == sizeBeforeIntegerPart) { + result.append(zero); + } + + delegate.formatted(INTEGER_FIELD, Field.INTEGER, Field.INTEGER, + iFieldStart, result.length(), result); + + // Output the decimal separator if we always do so. + int sStart = result.length(); + if (decimalSeparatorAlwaysShown || fractionPresent) { + result.append(decimal); + } + + if (sStart != result.length()) { + delegate.formatted(Field.DECIMAL_SEPARATOR, + Field.DECIMAL_SEPARATOR, + sStart, result.length(), result); + } + int fFieldStart = result.length(); + + for (int i=0; i < maxFraDigits; ++i) { + // Here is where we escape from the loop. We escape if we've + // output the maximum fraction digits (specified in the for + // expression above). + // We also stop when we've output the minimum digits and either: + // we have an integer, so there is no fractional stuff to + // display, or we're out of significant digits. + if (i >= minFraDigits && + (isInteger || digitIndex >= digitList.count)) { + break; + } + + // Output leading fractional zeros. These are zeros that come + // after the decimal but before any significant digits. These + // are only output if abs(number being formatted) < 1.0. + if (-1-i > (digitList.decimalAt-1)) { + result.append(zero); + continue; + } + + // Output a digit, if we have any precision left, or a + // zero if we don't. We don't want to output noise digits. + if (!isInteger && digitIndex < digitList.count) { + result.append((char)(digitList.digits[digitIndex++] + zeroDelta)); + } else { + result.append(zero); + } + } + + // Record field information for caller. + delegate.formatted(FRACTION_FIELD, Field.FRACTION, Field.FRACTION, + fFieldStart, result.length(), result); + } + + if (isNegative) { + append(result, negativeSuffix, delegate, + getNegativeSuffixFieldPositions(), Field.SIGN); + } else { + append(result, positiveSuffix, delegate, + getPositiveSuffixFieldPositions(), Field.SIGN); + } + + return result; + } + + /** + * Appends the String string to result. + * delegate is notified of all the + * FieldPositions in positions. + *

+ * If one of the FieldPositions in positions + * identifies a SIGN attribute, it is mapped to + * signAttribute. This is used + * to map the SIGN attribute to the EXPONENT + * attribute as necessary. + *

+ * This is used by subformat to add the prefix/suffix. + */ + private void append(StringBuffer result, String string, + FieldDelegate delegate, + FieldPosition[] positions, + Format.Field signAttribute) { + int start = result.length(); + + if (string.length() > 0) { + result.append(string); + for (int counter = 0, max = positions.length; counter < max; + counter++) { + FieldPosition fp = positions[counter]; + Format.Field attribute = fp.getFieldAttribute(); + + if (attribute == Field.SIGN) { + attribute = signAttribute; + } + delegate.formatted(attribute, attribute, + start + fp.getBeginIndex(), + start + fp.getEndIndex(), result); + } + } + } + + /** + * Parses text from a string to produce a Number. + *

+ * The method attempts to parse text starting at the index given by + * pos. + * If parsing succeeds, then the index of pos is updated + * to the index after the last character used (parsing does not necessarily + * use all characters up to the end of the string), and the parsed + * number is returned. The updated pos can be used to + * indicate the starting point for the next call to this method. + * If an error occurs, then the index of pos is not + * changed, the error index of pos is set to the index of + * the character where the error occurred, and null is returned. + *

+ * The subclass returned depends on the value of {@link #isParseBigDecimal} + * as well as on the string being parsed. + *

    + *
  • If isParseBigDecimal() is false (the default), + * most integer values are returned as Long + * objects, no matter how they are written: "17" and + * "17.000" both parse to Long(17). + * Values that cannot fit into a Long are returned as + * Doubles. This includes values with a fractional part, + * infinite values, NaN, and the value -0.0. + * DecimalFormat does not decide whether to + * return a Double or a Long based on the + * presence of a decimal separator in the source string. Doing so + * would prevent integers that overflow the mantissa of a double, + * such as "-9,223,372,036,854,775,808.00", from being + * parsed accurately. + *

    + * Callers may use the Number methods + * doubleValue, longValue, etc., to obtain + * the type they want. + *

  • If isParseBigDecimal() is true, values are returned + * as BigDecimal objects. The values are the ones + * constructed by {@link java.math.BigDecimal#BigDecimal(String)} + * for corresponding strings in locale-independent format. The + * special cases negative and positive infinity and NaN are returned + * as Double instances holding the values of the + * corresponding Double constants. + *
+ *

+ * DecimalFormat parses all Unicode characters that represent + * decimal digits, as defined by Character.digit(). In + * addition, DecimalFormat also recognizes as digits the ten + * consecutive characters starting with the localized zero digit defined in + * the DecimalFormatSymbols object. + * + * @param text the string to be parsed + * @param pos A ParsePosition object with index and error + * index information as described above. + * @return the parsed value, or null if the parse fails + * @exception NullPointerException if text or + * pos is null. + */ + @Override + public Number parse(String text, ParsePosition pos) { + // special case NaN + if (text.regionMatches(pos.index, symbols.getNaN(), 0, symbols.getNaN().length())) { + pos.index = pos.index + symbols.getNaN().length(); + return new Double(Double.NaN); + } + + boolean[] status = new boolean[STATUS_LENGTH]; + if (!subparse(text, pos, positivePrefix, negativePrefix, digitList, false, status)) { + return null; + } + + // special case INFINITY + if (status[STATUS_INFINITE]) { + if (status[STATUS_POSITIVE] == (multiplier >= 0)) { + return new Double(Double.POSITIVE_INFINITY); + } else { + return new Double(Double.NEGATIVE_INFINITY); + } + } + + if (multiplier == 0) { + if (digitList.isZero()) { + return new Double(Double.NaN); + } else if (status[STATUS_POSITIVE]) { + return new Double(Double.POSITIVE_INFINITY); + } else { + return new Double(Double.NEGATIVE_INFINITY); + } + } + + if (isParseBigDecimal()) { + BigDecimal bigDecimalResult = digitList.getBigDecimal(); + + if (multiplier != 1) { + try { + bigDecimalResult = bigDecimalResult.divide(getBigDecimalMultiplier()); + } + catch (ArithmeticException e) { // non-terminating decimal expansion + bigDecimalResult = bigDecimalResult.divide(getBigDecimalMultiplier(), roundingMode); + } + } + + if (!status[STATUS_POSITIVE]) { + bigDecimalResult = bigDecimalResult.negate(); + } + return bigDecimalResult; + } else { + boolean gotDouble = true; + boolean gotLongMinimum = false; + double doubleResult = 0.0; + long longResult = 0; + + // Finally, have DigitList parse the digits into a value. + if (digitList.fitsIntoLong(status[STATUS_POSITIVE], isParseIntegerOnly())) { + gotDouble = false; + longResult = digitList.getLong(); + if (longResult < 0) { // got Long.MIN_VALUE + gotLongMinimum = true; + } + } else { + doubleResult = digitList.getDouble(); + } + + // Divide by multiplier. We have to be careful here not to do + // unneeded conversions between double and long. + if (multiplier != 1) { + if (gotDouble) { + doubleResult /= multiplier; + } else { + // Avoid converting to double if we can + if (longResult % multiplier == 0) { + longResult /= multiplier; + } else { + doubleResult = ((double)longResult) / multiplier; + gotDouble = true; + } + } + } + + if (!status[STATUS_POSITIVE] && !gotLongMinimum) { + doubleResult = -doubleResult; + longResult = -longResult; + } + + // At this point, if we divided the result by the multiplier, the + // result may fit into a long. We check for this case and return + // a long if possible. + // We must do this AFTER applying the negative (if appropriate) + // in order to handle the case of LONG_MIN; otherwise, if we do + // this with a positive value -LONG_MIN, the double is > 0, but + // the long is < 0. We also must retain a double in the case of + // -0.0, which will compare as == to a long 0 cast to a double + // (bug 4162852). + if (multiplier != 1 && gotDouble) { + longResult = (long)doubleResult; + gotDouble = ((doubleResult != (double)longResult) || + (doubleResult == 0.0 && 1/doubleResult < 0.0)) && + !isParseIntegerOnly(); + } + + return gotDouble ? + (Number)new Double(doubleResult) : (Number)new Long(longResult); + } + } + + /** + * Return a BigInteger multiplier. + */ + private BigInteger getBigIntegerMultiplier() { + if (bigIntegerMultiplier == null) { + bigIntegerMultiplier = BigInteger.valueOf(multiplier); + } + return bigIntegerMultiplier; + } + private transient BigInteger bigIntegerMultiplier; + + /** + * Return a BigDecimal multiplier. + */ + private BigDecimal getBigDecimalMultiplier() { + if (bigDecimalMultiplier == null) { + bigDecimalMultiplier = new BigDecimal(multiplier); + } + return bigDecimalMultiplier; + } + private transient BigDecimal bigDecimalMultiplier; + + private static final int STATUS_INFINITE = 0; + private static final int STATUS_POSITIVE = 1; + private static final int STATUS_LENGTH = 2; + + /** + * Parse the given text into a number. The text is parsed beginning at + * parsePosition, until an unparseable character is seen. + * @param text The string to parse. + * @param parsePosition The position at which to being parsing. Upon + * return, the first unparseable character. + * @param digits The DigitList to set to the parsed value. + * @param isExponent If true, parse an exponent. This means no + * infinite values and integer only. + * @param status Upon return contains boolean status flags indicating + * whether the value was infinite and whether it was positive. + */ + private final boolean subparse(String text, ParsePosition parsePosition, + String positivePrefix, String negativePrefix, + DigitList digits, boolean isExponent, + boolean status[]) { + int position = parsePosition.index; + int oldStart = parsePosition.index; + int backup; + boolean gotPositive, gotNegative; + + // check for positivePrefix; take longest + gotPositive = text.regionMatches(position, positivePrefix, 0, + positivePrefix.length()); + gotNegative = text.regionMatches(position, negativePrefix, 0, + negativePrefix.length()); + + if (gotPositive && gotNegative) { + if (positivePrefix.length() > negativePrefix.length()) { + gotNegative = false; + } else if (positivePrefix.length() < negativePrefix.length()) { + gotPositive = false; + } + } + + if (gotPositive) { + position += positivePrefix.length(); + } else if (gotNegative) { + position += negativePrefix.length(); + } else { + parsePosition.errorIndex = position; + return false; + } + + // process digits or Inf, find decimal position + status[STATUS_INFINITE] = false; + if (!isExponent && text.regionMatches(position,symbols.getInfinity(),0, + symbols.getInfinity().length())) { + position += symbols.getInfinity().length(); + status[STATUS_INFINITE] = true; + } else { + // We now have a string of digits, possibly with grouping symbols, + // and decimal points. We want to process these into a DigitList. + // We don't want to put a bunch of leading zeros into the DigitList + // though, so we keep track of the location of the decimal point, + // put only significant digits into the DigitList, and adjust the + // exponent as needed. + + digits.decimalAt = digits.count = 0; + char zero = symbols.getZeroDigit(); + char decimal = isCurrencyFormat ? + symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + char grouping = symbols.getGroupingSeparator(); + String exponentString = symbols.getExponentSeparator(); + boolean sawDecimal = false; + boolean sawExponent = false; + boolean sawDigit = false; + int exponent = 0; // Set to the exponent value, if any + + // We have to track digitCount ourselves, because digits.count will + // pin when the maximum allowable digits is reached. + int digitCount = 0; + + backup = -1; + for (; position < text.length(); ++position) { + char ch = text.charAt(position); + + /* We recognize all digit ranges, not only the Latin digit range + * '0'..'9'. We do so by using the Character.digit() method, + * which converts a valid Unicode digit to the range 0..9. + * + * The character 'ch' may be a digit. If so, place its value + * from 0 to 9 in 'digit'. First try using the locale digit, + * which may or MAY NOT be a standard Unicode digit range. If + * this fails, try using the standard Unicode digit ranges by + * calling Character.digit(). If this also fails, digit will + * have a value outside the range 0..9. + */ + int digit = ch - zero; + if (digit < 0 || digit > 9) { + digit = Character.digit(ch, 10); + } + + if (digit == 0) { + // Cancel out backup setting (see grouping handler below) + backup = -1; // Do this BEFORE continue statement below!!! + sawDigit = true; + + // Handle leading zeros + if (digits.count == 0) { + // Ignore leading zeros in integer part of number. + if (!sawDecimal) { + continue; + } + + // If we have seen the decimal, but no significant + // digits yet, then we account for leading zeros by + // decrementing the digits.decimalAt into negative + // values. + --digits.decimalAt; + } else { + ++digitCount; + digits.append((char)(digit + '0')); + } + } else if (digit > 0 && digit <= 9) { // [sic] digit==0 handled above + sawDigit = true; + ++digitCount; + digits.append((char)(digit + '0')); + + // Cancel out backup setting (see grouping handler below) + backup = -1; + } else if (!isExponent && ch == decimal) { + // If we're only parsing integers, or if we ALREADY saw the + // decimal, then don't parse this one. + if (isParseIntegerOnly() || sawDecimal) { + break; + } + digits.decimalAt = digitCount; // Not digits.count! + sawDecimal = true; + } else if (!isExponent && ch == grouping && isGroupingUsed()) { + if (sawDecimal) { + break; + } + // Ignore grouping characters, if we are using them, but + // require that they be followed by a digit. Otherwise + // we backup and reprocess them. + backup = position; + } else if (!isExponent && text.regionMatches(position, exponentString, 0, exponentString.length()) + && !sawExponent) { + // Process the exponent by recursively calling this method. + ParsePosition pos = new ParsePosition(position + exponentString.length()); + boolean[] stat = new boolean[STATUS_LENGTH]; + DigitList exponentDigits = new DigitList(); + + if (subparse(text, pos, "", Character.toString(symbols.getMinusSign()), exponentDigits, true, stat) && + exponentDigits.fitsIntoLong(stat[STATUS_POSITIVE], true)) { + position = pos.index; // Advance past the exponent + exponent = (int)exponentDigits.getLong(); + if (!stat[STATUS_POSITIVE]) { + exponent = -exponent; + } + sawExponent = true; + } + break; // Whether we fail or succeed, we exit this loop + } else { + break; + } + } + + if (backup != -1) { + position = backup; + } + + // If there was no decimal point we have an integer + if (!sawDecimal) { + digits.decimalAt = digitCount; // Not digits.count! + } + + // Adjust for exponent, if any + digits.decimalAt += exponent; + + // If none of the text string was recognized. For example, parse + // "x" with pattern "#0.00" (return index and error index both 0) + // parse "$" with pattern "$#0.00". (return index 0 and error + // index 1). + if (!sawDigit && digitCount == 0) { + parsePosition.index = oldStart; + parsePosition.errorIndex = oldStart; + return false; + } + } + + // check for suffix + if (!isExponent) { + if (gotPositive) { + gotPositive = text.regionMatches(position,positiveSuffix,0, + positiveSuffix.length()); + } + if (gotNegative) { + gotNegative = text.regionMatches(position,negativeSuffix,0, + negativeSuffix.length()); + } + + // if both match, take longest + if (gotPositive && gotNegative) { + if (positiveSuffix.length() > negativeSuffix.length()) { + gotNegative = false; + } else if (positiveSuffix.length() < negativeSuffix.length()) { + gotPositive = false; + } + } + + // fail if neither or both + if (gotPositive == gotNegative) { + parsePosition.errorIndex = position; + return false; + } + + parsePosition.index = position + + (gotPositive ? positiveSuffix.length() : negativeSuffix.length()); // mark success! + } else { + parsePosition.index = position; + } + + status[STATUS_POSITIVE] = gotPositive; + if (parsePosition.index == oldStart) { + parsePosition.errorIndex = position; + return false; + } + return true; + } + + /** + * Returns a copy of the decimal format symbols, which is generally not + * changed by the programmer or user. + * @return a copy of the desired DecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + public DecimalFormatSymbols getDecimalFormatSymbols() { + try { + // don't allow multiple references + return (DecimalFormatSymbols) symbols.clone(); + } catch (Exception foo) { + return null; // should never happen + } + } + + + /** + * Sets the decimal format symbols, which is generally not changed + * by the programmer or user. + * @param newSymbols desired DecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) { + try { + // don't allow multiple references + symbols = (DecimalFormatSymbols) newSymbols.clone(); + expandAffixes(); + fastPathCheckNeeded = true; + } catch (Exception foo) { + // should never happen + } + } + + /** + * Get the positive prefix. + *

Examples: +123, $123, sFr123 + * + * @return the positive prefix + */ + public String getPositivePrefix () { + return positivePrefix; + } + + /** + * Set the positive prefix. + *

Examples: +123, $123, sFr123 + * + * @param newValue the new positive prefix + */ + public void setPositivePrefix (String newValue) { + positivePrefix = newValue; + posPrefixPattern = null; + positivePrefixFieldPositions = null; + fastPathCheckNeeded = true; + } + + /** + * Returns the FieldPositions of the fields in the prefix used for + * positive numbers. This is not used if the user has explicitly set + * a positive prefix via setPositivePrefix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getPositivePrefixFieldPositions() { + if (positivePrefixFieldPositions == null) { + if (posPrefixPattern != null) { + positivePrefixFieldPositions = expandAffix(posPrefixPattern); + } else { + positivePrefixFieldPositions = EmptyFieldPositionArray; + } + } + return positivePrefixFieldPositions; + } + + /** + * Get the negative prefix. + *

Examples: -123, ($123) (with negative suffix), sFr-123 + * + * @return the negative prefix + */ + public String getNegativePrefix () { + return negativePrefix; + } + + /** + * Set the negative prefix. + *

Examples: -123, ($123) (with negative suffix), sFr-123 + * + * @param newValue the new negative prefix + */ + public void setNegativePrefix (String newValue) { + negativePrefix = newValue; + negPrefixPattern = null; + fastPathCheckNeeded = true; + } + + /** + * Returns the FieldPositions of the fields in the prefix used for + * negative numbers. This is not used if the user has explicitly set + * a negative prefix via setNegativePrefix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getNegativePrefixFieldPositions() { + if (negativePrefixFieldPositions == null) { + if (negPrefixPattern != null) { + negativePrefixFieldPositions = expandAffix(negPrefixPattern); + } else { + negativePrefixFieldPositions = EmptyFieldPositionArray; + } + } + return negativePrefixFieldPositions; + } + + /** + * Get the positive suffix. + *

Example: 123% + * + * @return the positive suffix + */ + public String getPositiveSuffix () { + return positiveSuffix; + } + + /** + * Set the positive suffix. + *

Example: 123% + * + * @param newValue the new positive suffix + */ + public void setPositiveSuffix (String newValue) { + positiveSuffix = newValue; + posSuffixPattern = null; + fastPathCheckNeeded = true; + } + + /** + * Returns the FieldPositions of the fields in the suffix used for + * positive numbers. This is not used if the user has explicitly set + * a positive suffix via setPositiveSuffix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getPositiveSuffixFieldPositions() { + if (positiveSuffixFieldPositions == null) { + if (posSuffixPattern != null) { + positiveSuffixFieldPositions = expandAffix(posSuffixPattern); + } else { + positiveSuffixFieldPositions = EmptyFieldPositionArray; + } + } + return positiveSuffixFieldPositions; + } + + /** + * Get the negative suffix. + *

Examples: -123%, ($123) (with positive suffixes) + * + * @return the negative suffix + */ + public String getNegativeSuffix () { + return negativeSuffix; + } + + /** + * Set the negative suffix. + *

Examples: 123% + * + * @param newValue the new negative suffix + */ + public void setNegativeSuffix (String newValue) { + negativeSuffix = newValue; + negSuffixPattern = null; + fastPathCheckNeeded = true; + } + + /** + * Returns the FieldPositions of the fields in the suffix used for + * negative numbers. This is not used if the user has explicitly set + * a negative suffix via setNegativeSuffix. This is + * lazily created. + * + * @return FieldPositions in positive prefix + */ + private FieldPosition[] getNegativeSuffixFieldPositions() { + if (negativeSuffixFieldPositions == null) { + if (negSuffixPattern != null) { + negativeSuffixFieldPositions = expandAffix(negSuffixPattern); + } else { + negativeSuffixFieldPositions = EmptyFieldPositionArray; + } + } + return negativeSuffixFieldPositions; + } + + /** + * Gets the multiplier for use in percent, per mille, and similar + * formats. + * + * @return the multiplier + * @see #setMultiplier(int) + */ + public int getMultiplier () { + return multiplier; + } + + /** + * Sets the multiplier for use in percent, per mille, and similar + * formats. + * For a percent format, set the multiplier to 100 and the suffixes to + * have '%' (for Arabic, use the Arabic percent sign). + * For a per mille format, set the multiplier to 1000 and the suffixes to + * have '\u2030'. + * + *

Example: with multiplier 100, 1.23 is formatted as "123", and + * "123" is parsed into 1.23. + * + * @param newValue the new multiplier + * @see #getMultiplier + */ + public void setMultiplier (int newValue) { + multiplier = newValue; + bigDecimalMultiplier = null; + bigIntegerMultiplier = null; + fastPathCheckNeeded = true; + } + + /** + * {@inheritDoc} + */ + @Override + public void setGroupingUsed(boolean newValue) { + super.setGroupingUsed(newValue); + fastPathCheckNeeded = true; + } + + /** + * Return the grouping size. Grouping size is the number of digits between + * grouping separators in the integer portion of a number. For example, + * in the number "123,456.78", the grouping size is 3. + * + * @return the grouping size + * @see #setGroupingSize + * @see java.text.NumberFormat#isGroupingUsed + * @see java.text.DecimalFormatSymbols#getGroupingSeparator + */ + public int getGroupingSize () { + return groupingSize; + } + + /** + * Set the grouping size. Grouping size is the number of digits between + * grouping separators in the integer portion of a number. For example, + * in the number "123,456.78", the grouping size is 3. + *
+ * The value passed in is converted to a byte, which may lose information. + * + * @param newValue the new grouping size + * @see #getGroupingSize + * @see java.text.NumberFormat#setGroupingUsed + * @see java.text.DecimalFormatSymbols#setGroupingSeparator + */ + public void setGroupingSize (int newValue) { + groupingSize = (byte)newValue; + fastPathCheckNeeded = true; + } + + /** + * Allows you to get the behavior of the decimal separator with integers. + * (The decimal separator will always appear with decimals.) + *

Example: Decimal ON: 12345 → 12345.; OFF: 12345 → 12345 + * + * @return {@code true} if the decimal separator is always shown; + * {@code false} otherwise + */ + public boolean isDecimalSeparatorAlwaysShown() { + return decimalSeparatorAlwaysShown; + } + + /** + * Allows you to set the behavior of the decimal separator with integers. + * (The decimal separator will always appear with decimals.) + *

Example: Decimal ON: 12345 → 12345.; OFF: 12345 → 12345 + * + * @param newValue {@code true} if the decimal separator is always shown; + * {@code false} otherwise + */ + public void setDecimalSeparatorAlwaysShown(boolean newValue) { + decimalSeparatorAlwaysShown = newValue; + fastPathCheckNeeded = true; + } + + /** + * Returns whether the {@link #parse(java.lang.String, java.text.ParsePosition)} + * method returns BigDecimal. The default value is false. + * + * @return {@code true} if the parse method returns BigDecimal; + * {@code false} otherwise + * @see #setParseBigDecimal + * @since 1.5 + */ + public boolean isParseBigDecimal() { + return parseBigDecimal; + } + + /** + * Sets whether the {@link #parse(java.lang.String, java.text.ParsePosition)} + * method returns BigDecimal. + * + * @param newValue {@code true} if the parse method returns BigDecimal; + * {@code false} otherwise + * @see #isParseBigDecimal + * @since 1.5 + */ + public void setParseBigDecimal(boolean newValue) { + parseBigDecimal = newValue; + } + + /** + * Standard override; no change in semantics. + */ + @Override + public Object clone() { + DecimalFormat other = (DecimalFormat) super.clone(); + other.symbols = (DecimalFormatSymbols) symbols.clone(); + other.digitList = (DigitList) digitList.clone(); + + // Fast-path is almost stateless algorithm. The only logical state is the + // isFastPath flag. In addition fastPathCheckNeeded is a sentinel flag + // that forces recalculation of all fast-path fields when set to true. + // + // There is thus no need to clone all the fast-path fields. + // We just only need to set fastPathCheckNeeded to true when cloning, + // and init fastPathData to null as if it were a truly new instance. + // Every fast-path field will be recalculated (only once) at next usage of + // fast-path algorithm. + other.fastPathCheckNeeded = true; + other.isFastPath = false; + other.fastPathData = null; + + return other; + } + + /** + * Overrides equals + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + return false; + if (!super.equals(obj)) + return false; // super does class check + DecimalFormat other = (DecimalFormat) obj; + return ((posPrefixPattern == other.posPrefixPattern && + positivePrefix.equals(other.positivePrefix)) + || (posPrefixPattern != null && + posPrefixPattern.equals(other.posPrefixPattern))) + && ((posSuffixPattern == other.posSuffixPattern && + positiveSuffix.equals(other.positiveSuffix)) + || (posSuffixPattern != null && + posSuffixPattern.equals(other.posSuffixPattern))) + && ((negPrefixPattern == other.negPrefixPattern && + negativePrefix.equals(other.negativePrefix)) + || (negPrefixPattern != null && + negPrefixPattern.equals(other.negPrefixPattern))) + && ((negSuffixPattern == other.negSuffixPattern && + negativeSuffix.equals(other.negativeSuffix)) + || (negSuffixPattern != null && + negSuffixPattern.equals(other.negSuffixPattern))) + && multiplier == other.multiplier + && groupingSize == other.groupingSize + && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown + && parseBigDecimal == other.parseBigDecimal + && useExponentialNotation == other.useExponentialNotation + && (!useExponentialNotation || + minExponentDigits == other.minExponentDigits) + && maximumIntegerDigits == other.maximumIntegerDigits + && minimumIntegerDigits == other.minimumIntegerDigits + && maximumFractionDigits == other.maximumFractionDigits + && minimumFractionDigits == other.minimumFractionDigits + && roundingMode == other.roundingMode + && symbols.equals(other.symbols); + } + + /** + * Overrides hashCode + */ + @Override + public int hashCode() { + return super.hashCode() * 37 + positivePrefix.hashCode(); + // just enough fields for a reasonable distribution + } + + /** + * Synthesizes a pattern string that represents the current state + * of this Format object. + * + * @return a pattern string + * @see #applyPattern + */ + public String toPattern() { + return toPattern( false ); + } + + /** + * Synthesizes a localized pattern string that represents the current + * state of this Format object. + * + * @return a localized pattern string + * @see #applyPattern + */ + public String toLocalizedPattern() { + return toPattern( true ); + } + + /** + * Expand the affix pattern strings into the expanded affix strings. If any + * affix pattern string is null, do not expand it. This method should be + * called any time the symbols or the affix patterns change in order to keep + * the expanded affix strings up to date. + */ + private void expandAffixes() { + // Reuse one StringBuffer for better performance + StringBuffer buffer = new StringBuffer(); + if (posPrefixPattern != null) { + positivePrefix = expandAffix(posPrefixPattern, buffer); + positivePrefixFieldPositions = null; + } + if (posSuffixPattern != null) { + positiveSuffix = expandAffix(posSuffixPattern, buffer); + positiveSuffixFieldPositions = null; + } + if (negPrefixPattern != null) { + negativePrefix = expandAffix(negPrefixPattern, buffer); + negativePrefixFieldPositions = null; + } + if (negSuffixPattern != null) { + negativeSuffix = expandAffix(negSuffixPattern, buffer); + negativeSuffixFieldPositions = null; + } + } + + /** + * Expand an affix pattern into an affix string. All characters in the + * pattern are literal unless prefixed by QUOTE. The following characters + * after QUOTE are recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, + * PATTERN_MINUS, and CURRENCY_SIGN. If CURRENCY_SIGN is doubled (QUOTE + + * CURRENCY_SIGN + CURRENCY_SIGN), it is interpreted as an ISO 4217 + * currency code. Any other character after a QUOTE represents itself. + * QUOTE must be followed by another character; QUOTE may not occur by + * itself at the end of the pattern. + * + * @param pattern the non-null, possibly empty pattern + * @param buffer a scratch StringBuffer; its contents will be lost + * @return the expanded equivalent of pattern + */ + private String expandAffix(String pattern, StringBuffer buffer) { + buffer.setLength(0); + for (int i=0; i positions = null; + int stringIndex = 0; + for (int i=0; i 0) { + if (positions == null) { + positions = new ArrayList<>(2); + } + FieldPosition fp = new FieldPosition(Field.CURRENCY); + fp.setBeginIndex(stringIndex); + fp.setEndIndex(stringIndex + string.length()); + positions.add(fp); + stringIndex += string.length(); + } + continue; + case PATTERN_PERCENT: + c = symbols.getPercent(); + field = -1; + fieldID = Field.PERCENT; + break; + case PATTERN_PER_MILLE: + c = symbols.getPerMill(); + field = -1; + fieldID = Field.PERMILLE; + break; + case PATTERN_MINUS: + c = symbols.getMinusSign(); + field = -1; + fieldID = Field.SIGN; + break; + } + if (fieldID != null) { + if (positions == null) { + positions = new ArrayList<>(2); + } + FieldPosition fp = new FieldPosition(fieldID, field); + fp.setBeginIndex(stringIndex); + fp.setEndIndex(stringIndex + 1); + positions.add(fp); + } + } + stringIndex++; + } + if (positions != null) { + return positions.toArray(EmptyFieldPositionArray); + } + return EmptyFieldPositionArray; + } + + /** + * Appends an affix pattern to the given StringBuffer, quoting special + * characters as needed. Uses the internal affix pattern, if that exists, + * or the literal affix, if the internal affix pattern is null. The + * appended string will generate the same affix pattern (or literal affix) + * when passed to toPattern(). + * + * @param buffer the affix string is appended to this + * @param affixPattern a pattern such as posPrefixPattern; may be null + * @param expAffix a corresponding expanded affix, such as positivePrefix. + * Ignored unless affixPattern is null. If affixPattern is null, then + * expAffix is appended as a literal affix. + * @param localized true if the appended pattern should contain localized + * pattern characters; otherwise, non-localized pattern chars are appended + */ + private void appendAffix(StringBuffer buffer, String affixPattern, + String expAffix, boolean localized) { + if (affixPattern == null) { + appendAffix(buffer, expAffix, localized); + } else { + int i; + for (int pos=0; pos pos) { + appendAffix(buffer, affixPattern.substring(pos, i), localized); + } + char c = affixPattern.charAt(++i); + ++i; + if (c == QUOTE) { + buffer.append(c); + // Fall through and append another QUOTE below + } else if (c == CURRENCY_SIGN && + i= 0 + || affix.indexOf(symbols.getGroupingSeparator()) >= 0 + || affix.indexOf(symbols.getDecimalSeparator()) >= 0 + || affix.indexOf(symbols.getPercent()) >= 0 + || affix.indexOf(symbols.getPerMill()) >= 0 + || affix.indexOf(symbols.getDigit()) >= 0 + || affix.indexOf(symbols.getPatternSeparator()) >= 0 + || affix.indexOf(symbols.getMinusSign()) >= 0 + || affix.indexOf(CURRENCY_SIGN) >= 0; + } else { + needQuote = affix.indexOf(PATTERN_ZERO_DIGIT) >= 0 + || affix.indexOf(PATTERN_GROUPING_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_DECIMAL_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_PERCENT) >= 0 + || affix.indexOf(PATTERN_PER_MILLE) >= 0 + || affix.indexOf(PATTERN_DIGIT) >= 0 + || affix.indexOf(PATTERN_SEPARATOR) >= 0 + || affix.indexOf(PATTERN_MINUS) >= 0 + || affix.indexOf(CURRENCY_SIGN) >= 0; + } + if (needQuote) buffer.append('\''); + if (affix.indexOf('\'') < 0) buffer.append(affix); + else { + for (int j=0; j= 0; --j) { + if (j == 1) + appendAffix(result, posPrefixPattern, positivePrefix, localized); + else appendAffix(result, negPrefixPattern, negativePrefix, localized); + int i; + int digitCount = useExponentialNotation + ? getMaximumIntegerDigits() + : Math.max(groupingSize, getMinimumIntegerDigits())+1; + for (i = digitCount; i > 0; --i) { + if (i != digitCount && isGroupingUsed() && groupingSize != 0 && + i % groupingSize == 0) { + result.append(localized ? symbols.getGroupingSeparator() : + PATTERN_GROUPING_SEPARATOR); + } + result.append(i <= getMinimumIntegerDigits() + ? (localized ? symbols.getZeroDigit() : PATTERN_ZERO_DIGIT) + : (localized ? symbols.getDigit() : PATTERN_DIGIT)); + } + if (getMaximumFractionDigits() > 0 || decimalSeparatorAlwaysShown) + result.append(localized ? symbols.getDecimalSeparator() : + PATTERN_DECIMAL_SEPARATOR); + for (i = 0; i < getMaximumFractionDigits(); ++i) { + if (i < getMinimumFractionDigits()) { + result.append(localized ? symbols.getZeroDigit() : + PATTERN_ZERO_DIGIT); + } else { + result.append(localized ? symbols.getDigit() : + PATTERN_DIGIT); + } + } + if (useExponentialNotation) + { + result.append(localized ? symbols.getExponentSeparator() : + PATTERN_EXPONENT); + for (i=0; i + * There is no limit to integer digits set + * by this routine, since that is the typical end-user desire; + * use setMaximumInteger if you want to set a real value. + * For negative numbers, use a second pattern, separated by a semicolon + *

Example "#,#00.0#" → 1,234.56 + *

This means a minimum of 2 integer digits, 1 fraction digit, and + * a maximum of 2 fraction digits. + *

Example: "#,#00.0#;(#,#00.0#)" for negatives in + * parentheses. + *

In negative patterns, the minimum and maximum counts are ignored; + * these are presumed to be set in the positive pattern. + * + * @param pattern a new pattern + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + */ + public void applyPattern(String pattern) { + applyPattern(pattern, false); + } + + /** + * Apply the given pattern to this Format object. The pattern + * is assumed to be in a localized notation. A pattern is a + * short-hand specification for the various formatting properties. + * These properties can also be changed individually through the + * various setter methods. + *

+ * There is no limit to integer digits set + * by this routine, since that is the typical end-user desire; + * use setMaximumInteger if you want to set a real value. + * For negative numbers, use a second pattern, separated by a semicolon + *

Example "#,#00.0#" → 1,234.56 + *

This means a minimum of 2 integer digits, 1 fraction digit, and + * a maximum of 2 fraction digits. + *

Example: "#,#00.0#;(#,#00.0#)" for negatives in + * parentheses. + *

In negative patterns, the minimum and maximum counts are ignored; + * these are presumed to be set in the positive pattern. + * + * @param pattern a new pattern + * @exception NullPointerException if pattern is null + * @exception IllegalArgumentException if the given pattern is invalid. + */ + public void applyLocalizedPattern(String pattern) { + applyPattern(pattern, true); + } + + /** + * Does the real work of applying a pattern. + */ + private void applyPattern(String pattern, boolean localized) { + char zeroDigit = PATTERN_ZERO_DIGIT; + char groupingSeparator = PATTERN_GROUPING_SEPARATOR; + char decimalSeparator = PATTERN_DECIMAL_SEPARATOR; + char percent = PATTERN_PERCENT; + char perMill = PATTERN_PER_MILLE; + char digit = PATTERN_DIGIT; + char separator = PATTERN_SEPARATOR; + String exponent = PATTERN_EXPONENT; + char minus = PATTERN_MINUS; + if (localized) { + zeroDigit = symbols.getZeroDigit(); + groupingSeparator = symbols.getGroupingSeparator(); + decimalSeparator = symbols.getDecimalSeparator(); + percent = symbols.getPercent(); + perMill = symbols.getPerMill(); + digit = symbols.getDigit(); + separator = symbols.getPatternSeparator(); + exponent = symbols.getExponentSeparator(); + minus = symbols.getMinusSign(); + } + boolean gotNegative = false; + decimalSeparatorAlwaysShown = false; + isCurrencyFormat = false; + useExponentialNotation = false; + + // Two variables are used to record the subrange of the pattern + // occupied by phase 1. This is used during the processing of the + // second pattern (the one representing negative numbers) to ensure + // that no deviation exists in phase 1 between the two patterns. + int phaseOneStart = 0; + int phaseOneLength = 0; + + int start = 0; + for (int j = 1; j >= 0 && start < pattern.length(); --j) { + boolean inQuote = false; + StringBuffer prefix = new StringBuffer(); + StringBuffer suffix = new StringBuffer(); + int decimalPos = -1; + int multiplier = 1; + int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0; + byte groupingCount = -1; + + // The phase ranges from 0 to 2. Phase 0 is the prefix. Phase 1 is + // the section of the pattern with digits, decimal separator, + // grouping characters. Phase 2 is the suffix. In phases 0 and 2, + // percent, per mille, and currency symbols are recognized and + // translated. The separation of the characters into phases is + // strictly enforced; if phase 1 characters are to appear in the + // suffix, for example, they must be quoted. + int phase = 0; + + // The affix is either the prefix or the suffix. + StringBuffer affix = prefix; + + for (int pos = start; pos < pattern.length(); ++pos) { + char ch = pattern.charAt(pos); + switch (phase) { + case 0: + case 2: + // Process the prefix / suffix characters + if (inQuote) { + // A quote within quotes indicates either the closing + // quote or two quotes, which is a quote literal. That + // is, we have the second quote in 'do' or 'don''t'. + if (ch == QUOTE) { + if ((pos+1) < pattern.length() && + pattern.charAt(pos+1) == QUOTE) { + ++pos; + affix.append("''"); // 'don''t' + } else { + inQuote = false; // 'do' + } + continue; + } + } else { + // Process unquoted characters seen in prefix or suffix + // phase. + if (ch == digit || + ch == zeroDigit || + ch == groupingSeparator || + ch == decimalSeparator) { + phase = 1; + if (j == 1) { + phaseOneStart = pos; + } + --pos; // Reprocess this character + continue; + } else if (ch == CURRENCY_SIGN) { + // Use lookahead to determine if the currency sign + // is doubled or not. + boolean doubled = (pos + 1) < pattern.length() && + pattern.charAt(pos + 1) == CURRENCY_SIGN; + if (doubled) { // Skip over the doubled character + ++pos; + } + isCurrencyFormat = true; + affix.append(doubled ? "'\u00A4\u00A4" : "'\u00A4"); + continue; + } else if (ch == QUOTE) { + // A quote outside quotes indicates either the + // opening quote or two quotes, which is a quote + // literal. That is, we have the first quote in 'do' + // or o''clock. + if (ch == QUOTE) { + if ((pos+1) < pattern.length() && + pattern.charAt(pos+1) == QUOTE) { + ++pos; + affix.append("''"); // o''clock + } else { + inQuote = true; // 'do' + } + continue; + } + } else if (ch == separator) { + // Don't allow separators before we see digit + // characters of phase 1, and don't allow separators + // in the second pattern (j == 0). + if (phase == 0 || j == 0) { + throw new IllegalArgumentException("Unquoted special character '" + + ch + "' in pattern \"" + pattern + '"'); + } + start = pos + 1; + pos = pattern.length(); + continue; + } + + // Next handle characters which are appended directly. + else if (ch == percent) { + if (multiplier != 1) { + throw new IllegalArgumentException("Too many percent/per mille characters in pattern \"" + + pattern + '"'); + } + multiplier = 100; + affix.append("'%"); + continue; + } else if (ch == perMill) { + if (multiplier != 1) { + throw new IllegalArgumentException("Too many percent/per mille characters in pattern \"" + + pattern + '"'); + } + multiplier = 1000; + affix.append("'\u2030"); + continue; + } else if (ch == minus) { + affix.append("'-"); + continue; + } + } + // Note that if we are within quotes, or if this is an + // unquoted, non-special character, then we usually fall + // through to here. + affix.append(ch); + break; + + case 1: + // Phase one must be identical in the two sub-patterns. We + // enforce this by doing a direct comparison. While + // processing the first sub-pattern, we just record its + // length. While processing the second, we compare + // characters. + if (j == 1) { + ++phaseOneLength; + } else { + if (--phaseOneLength == 0) { + phase = 2; + affix = suffix; + } + continue; + } + + // Process the digits, decimal, and grouping characters. We + // record five pieces of information. We expect the digits + // to occur in the pattern ####0000.####, and we record the + // number of left digits, zero (central) digits, and right + // digits. The position of the last grouping character is + // recorded (should be somewhere within the first two blocks + // of characters), as is the position of the decimal point, + // if any (should be in the zero digits). If there is no + // decimal point, then there should be no right digits. + if (ch == digit) { + if (zeroDigitCount > 0) { + ++digitRightCount; + } else { + ++digitLeftCount; + } + if (groupingCount >= 0 && decimalPos < 0) { + ++groupingCount; + } + } else if (ch == zeroDigit) { + if (digitRightCount > 0) { + throw new IllegalArgumentException("Unexpected '0' in pattern \"" + + pattern + '"'); + } + ++zeroDigitCount; + if (groupingCount >= 0 && decimalPos < 0) { + ++groupingCount; + } + } else if (ch == groupingSeparator) { + groupingCount = 0; + } else if (ch == decimalSeparator) { + if (decimalPos >= 0) { + throw new IllegalArgumentException("Multiple decimal separators in pattern \"" + + pattern + '"'); + } + decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; + } else if (pattern.regionMatches(pos, exponent, 0, exponent.length())){ + if (useExponentialNotation) { + throw new IllegalArgumentException("Multiple exponential " + + "symbols in pattern \"" + pattern + '"'); + } + useExponentialNotation = true; + minExponentDigits = 0; + + // Use lookahead to parse out the exponential part + // of the pattern, then jump into phase 2. + pos = pos+exponent.length(); + while (pos < pattern.length() && + pattern.charAt(pos) == zeroDigit) { + ++minExponentDigits; + ++phaseOneLength; + ++pos; + } + + if ((digitLeftCount + zeroDigitCount) < 1 || + minExponentDigits < 1) { + throw new IllegalArgumentException("Malformed exponential " + + "pattern \"" + pattern + '"'); + } + + // Transition to phase 2 + phase = 2; + affix = suffix; + --pos; + continue; + } else { + phase = 2; + affix = suffix; + --pos; + --phaseOneLength; + continue; + } + break; + } + } + + // Handle patterns with no '0' pattern character. These patterns + // are legal, but must be interpreted. "##.###" -> "#0.###". + // ".###" -> ".0##". + /* We allow patterns of the form "####" to produce a zeroDigitCount + * of zero (got that?); although this seems like it might make it + * possible for format() to produce empty strings, format() checks + * for this condition and outputs a zero digit in this situation. + * Having a zeroDigitCount of zero yields a minimum integer digits + * of zero, which allows proper round-trip patterns. That is, we + * don't want "#" to become "#0" when toPattern() is called (even + * though that's what it really is, semantically). + */ + if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { + // Handle "###.###" and "###." and ".###" + int n = decimalPos; + if (n == 0) { // Handle ".###" + ++n; + } + digitRightCount = digitLeftCount - n; + digitLeftCount = n - 1; + zeroDigitCount = 1; + } + + // Do syntax checking on the digits. + if ((decimalPos < 0 && digitRightCount > 0) || + (decimalPos >= 0 && (decimalPos < digitLeftCount || + decimalPos > (digitLeftCount + zeroDigitCount))) || + groupingCount == 0 || inQuote) { + throw new IllegalArgumentException("Malformed pattern \"" + + pattern + '"'); + } + + if (j == 1) { + posPrefixPattern = prefix.toString(); + posSuffixPattern = suffix.toString(); + negPrefixPattern = posPrefixPattern; // assume these for now + negSuffixPattern = posSuffixPattern; + int digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount; + /* The effectiveDecimalPos is the position the decimal is at or + * would be at if there is no decimal. Note that if decimalPos<0, + * then digitTotalCount == digitLeftCount + zeroDigitCount. + */ + int effectiveDecimalPos = decimalPos >= 0 ? + decimalPos : digitTotalCount; + setMinimumIntegerDigits(effectiveDecimalPos - digitLeftCount); + setMaximumIntegerDigits(useExponentialNotation ? + digitLeftCount + getMinimumIntegerDigits() : + MAXIMUM_INTEGER_DIGITS); + setMaximumFractionDigits(decimalPos >= 0 ? + (digitTotalCount - decimalPos) : 0); + setMinimumFractionDigits(decimalPos >= 0 ? + (digitLeftCount + zeroDigitCount - decimalPos) : 0); + setGroupingUsed(groupingCount > 0); + this.groupingSize = (groupingCount > 0) ? groupingCount : 0; + this.multiplier = multiplier; + setDecimalSeparatorAlwaysShown(decimalPos == 0 || + decimalPos == digitTotalCount); + } else { + negPrefixPattern = prefix.toString(); + negSuffixPattern = suffix.toString(); + gotNegative = true; + } + } + + if (pattern.length() == 0) { + posPrefixPattern = posSuffixPattern = ""; + setMinimumIntegerDigits(0); + setMaximumIntegerDigits(MAXIMUM_INTEGER_DIGITS); + setMinimumFractionDigits(0); + setMaximumFractionDigits(MAXIMUM_FRACTION_DIGITS); + } + + // If there was no negative pattern, or if the negative pattern is + // identical to the positive pattern, then prepend the minus sign to + // the positive pattern to form the negative pattern. + if (!gotNegative || + (negPrefixPattern.equals(posPrefixPattern) + && negSuffixPattern.equals(posSuffixPattern))) { + negSuffixPattern = posSuffixPattern; + negPrefixPattern = "'-" + posPrefixPattern; + } + + expandAffixes(); + } + + /** + * Sets the maximum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 309 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMaximumIntegerDigits + */ + @Override + public void setMaximumIntegerDigits(int newValue) { + maximumIntegerDigits = Math.min(Math.max(0, newValue), MAXIMUM_INTEGER_DIGITS); + super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : maximumIntegerDigits); + if (minimumIntegerDigits > maximumIntegerDigits) { + minimumIntegerDigits = maximumIntegerDigits; + super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : minimumIntegerDigits); + } + fastPathCheckNeeded = true; + } + + /** + * Sets the minimum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 309 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMinimumIntegerDigits + */ + @Override + public void setMinimumIntegerDigits(int newValue) { + minimumIntegerDigits = Math.min(Math.max(0, newValue), MAXIMUM_INTEGER_DIGITS); + super.setMinimumIntegerDigits((minimumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : minimumIntegerDigits); + if (minimumIntegerDigits > maximumIntegerDigits) { + maximumIntegerDigits = minimumIntegerDigits; + super.setMaximumIntegerDigits((maximumIntegerDigits > DOUBLE_INTEGER_DIGITS) ? + DOUBLE_INTEGER_DIGITS : maximumIntegerDigits); + } + fastPathCheckNeeded = true; + } + + /** + * Sets the maximum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 340 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMaximumFractionDigits + */ + @Override + public void setMaximumFractionDigits(int newValue) { + maximumFractionDigits = Math.min(Math.max(0, newValue), MAXIMUM_FRACTION_DIGITS); + super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : maximumFractionDigits); + if (minimumFractionDigits > maximumFractionDigits) { + minimumFractionDigits = maximumFractionDigits; + super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : minimumFractionDigits); + } + fastPathCheckNeeded = true; + } + + /** + * Sets the minimum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of newValue and + * 340 is used. Negative input values are replaced with 0. + * @see NumberFormat#setMinimumFractionDigits + */ + @Override + public void setMinimumFractionDigits(int newValue) { + minimumFractionDigits = Math.min(Math.max(0, newValue), MAXIMUM_FRACTION_DIGITS); + super.setMinimumFractionDigits((minimumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : minimumFractionDigits); + if (minimumFractionDigits > maximumFractionDigits) { + maximumFractionDigits = minimumFractionDigits; + super.setMaximumFractionDigits((maximumFractionDigits > DOUBLE_FRACTION_DIGITS) ? + DOUBLE_FRACTION_DIGITS : maximumFractionDigits); + } + fastPathCheckNeeded = true; + } + + /** + * Gets the maximum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 309 is used. + * @see #setMaximumIntegerDigits + */ + @Override + public int getMaximumIntegerDigits() { + return maximumIntegerDigits; + } + + /** + * Gets the minimum number of digits allowed in the integer portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 309 is used. + * @see #setMinimumIntegerDigits + */ + @Override + public int getMinimumIntegerDigits() { + return minimumIntegerDigits; + } + + /** + * Gets the maximum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 340 is used. + * @see #setMaximumFractionDigits + */ + @Override + public int getMaximumFractionDigits() { + return maximumFractionDigits; + } + + /** + * Gets the minimum number of digits allowed in the fraction portion of a + * number. + * For formatting numbers other than BigInteger and + * BigDecimal objects, the lower of the return value and + * 340 is used. + * @see #setMinimumFractionDigits + */ + @Override + public int getMinimumFractionDigits() { + return minimumFractionDigits; + } + + /** + * Gets the currency used by this decimal format when formatting + * currency values. + * The currency is obtained by calling + * {@link DecimalFormatSymbols#getCurrency DecimalFormatSymbols.getCurrency} + * on this number format's symbols. + * + * @return the currency used by this decimal format, or null + * @since 1.4 + */ + @Override + public Currency getCurrency() { + return symbols.getCurrency(); + } + + /** + * Sets the currency used by this number format when formatting + * currency values. This does not update the minimum or maximum + * number of fraction digits used by the number format. + * The currency is set by calling + * {@link DecimalFormatSymbols#setCurrency DecimalFormatSymbols.setCurrency} + * on this number format's symbols. + * + * @param currency the new currency to be used by this decimal format + * @exception NullPointerException if currency is null + * @since 1.4 + */ + @Override + public void setCurrency(Currency currency) { + if (currency != symbols.getCurrency()) { + symbols.setCurrency(currency); + if (isCurrencyFormat) { + expandAffixes(); + } + } + fastPathCheckNeeded = true; + } + + /** + * Gets the {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @return The RoundingMode used for this DecimalFormat. + * @see #setRoundingMode(RoundingMode) + * @since 1.6 + */ + @Override + public RoundingMode getRoundingMode() { + return roundingMode; + } + + /** + * Sets the {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @param roundingMode The RoundingMode to be used + * @see #getRoundingMode() + * @exception NullPointerException if roundingMode is null. + * @since 1.6 + */ + @Override + public void setRoundingMode(RoundingMode roundingMode) { + if (roundingMode == null) { + throw new NullPointerException(); + } + + this.roundingMode = roundingMode; + digitList.setRoundingMode(roundingMode); + fastPathCheckNeeded = true; + } + + /** + * Reads the default serializable fields from the stream and performs + * validations and adjustments for older serialized versions. The + * validations and adjustments are: + *

    + *
  1. + * Verify that the superclass's digit count fields correctly reflect + * the limits imposed on formatting numbers other than + * BigInteger and BigDecimal objects. These + * limits are stored in the superclass for serialization compatibility + * with older versions, while the limits for BigInteger and + * BigDecimal objects are kept in this class. + * If, in the superclass, the minimum or maximum integer digit count is + * larger than DOUBLE_INTEGER_DIGITS or if the minimum or + * maximum fraction digit count is larger than + * DOUBLE_FRACTION_DIGITS, then the stream data is invalid + * and this method throws an InvalidObjectException. + *
  2. + * If serialVersionOnStream is less than 4, initialize + * roundingMode to {@link java.math.RoundingMode#HALF_EVEN + * RoundingMode.HALF_EVEN}. This field is new with version 4. + *
  3. + * If serialVersionOnStream is less than 3, then call + * the setters for the minimum and maximum integer and fraction digits with + * the values of the corresponding superclass getters to initialize the + * fields in this class. The fields in this class are new with version 3. + *
  4. + * If serialVersionOnStream is less than 1, indicating that + * the stream was written by JDK 1.1, initialize + * useExponentialNotation + * to false, since it was not present in JDK 1.1. + *
  5. + * Set serialVersionOnStream to the maximum allowed value so + * that default serialization will work properly if this object is streamed + * out again. + *
+ * + *

Stream versions older than 2 will not have the affix pattern variables + * posPrefixPattern etc. As a result, they will be initialized + * to null, which means the affix strings will be taken as + * literal values. This is exactly what we want, since that corresponds to + * the pre-version-2 behavior. + */ + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + digitList = new DigitList(); + + // We force complete fast-path reinitialization when the instance is + // deserialized. See clone() comment on fastPathCheckNeeded. + fastPathCheckNeeded = true; + isFastPath = false; + fastPathData = null; + + if (serialVersionOnStream < 4) { + setRoundingMode(RoundingMode.HALF_EVEN); + } else { + setRoundingMode(getRoundingMode()); + } + + // We only need to check the maximum counts because NumberFormat + // .readObject has already ensured that the maximum is greater than the + // minimum count. + if (super.getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS || + super.getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) { + throw new InvalidObjectException("Digit count out of range"); + } + if (serialVersionOnStream < 3) { + setMaximumIntegerDigits(super.getMaximumIntegerDigits()); + setMinimumIntegerDigits(super.getMinimumIntegerDigits()); + setMaximumFractionDigits(super.getMaximumFractionDigits()); + setMinimumFractionDigits(super.getMinimumFractionDigits()); + } + if (serialVersionOnStream < 1) { + // Didn't have exponential fields + useExponentialNotation = false; + } + serialVersionOnStream = currentSerialVersion; + } + + //---------------------------------------------------------------------- + // INSTANCE VARIABLES + //---------------------------------------------------------------------- + + private transient DigitList digitList = new DigitList(); + + /** + * The symbol used as a prefix when formatting positive numbers, e.g. "+". + * + * @serial + * @see #getPositivePrefix + */ + private String positivePrefix = ""; + + /** + * The symbol used as a suffix when formatting positive numbers. + * This is often an empty string. + * + * @serial + * @see #getPositiveSuffix + */ + private String positiveSuffix = ""; + + /** + * The symbol used as a prefix when formatting negative numbers, e.g. "-". + * + * @serial + * @see #getNegativePrefix + */ + private String negativePrefix = "-"; + + /** + * The symbol used as a suffix when formatting negative numbers. + * This is often an empty string. + * + * @serial + * @see #getNegativeSuffix + */ + private String negativeSuffix = ""; + + /** + * The prefix pattern for non-negative numbers. This variable corresponds + * to positivePrefix. + * + *

This pattern is expanded by the method expandAffix() to + * positivePrefix to update the latter to reflect changes in + * symbols. If this variable is null then + * positivePrefix is taken as a literal value that does not + * change when symbols changes. This variable is always + * null for DecimalFormat objects older than + * stream version 2 restored from stream. + * + * @serial + * @since 1.3 + */ + private String posPrefixPattern; + + /** + * The suffix pattern for non-negative numbers. This variable corresponds + * to positiveSuffix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String posSuffixPattern; + + /** + * The prefix pattern for negative numbers. This variable corresponds + * to negativePrefix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String negPrefixPattern; + + /** + * The suffix pattern for negative numbers. This variable corresponds + * to negativeSuffix. This variable is analogous to + * posPrefixPattern; see that variable for further + * documentation. + * + * @serial + * @since 1.3 + */ + private String negSuffixPattern; + + /** + * The multiplier for use in percent, per mille, etc. + * + * @serial + * @see #getMultiplier + */ + private int multiplier = 1; + + /** + * The number of digits between grouping separators in the integer + * portion of a number. Must be greater than 0 if + * NumberFormat.groupingUsed is true. + * + * @serial + * @see #getGroupingSize + * @see java.text.NumberFormat#isGroupingUsed + */ + private byte groupingSize = 3; // invariant, > 0 if useThousands + + /** + * If true, forces the decimal separator to always appear in a formatted + * number, even if the fractional part of the number is zero. + * + * @serial + * @see #isDecimalSeparatorAlwaysShown + */ + private boolean decimalSeparatorAlwaysShown = false; + + /** + * If true, parse returns BigDecimal wherever possible. + * + * @serial + * @see #isParseBigDecimal + * @since 1.5 + */ + private boolean parseBigDecimal = false; + + + /** + * True if this object represents a currency format. This determines + * whether the monetary decimal separator is used instead of the normal one. + */ + private transient boolean isCurrencyFormat = false; + + /** + * The DecimalFormatSymbols object used by this format. + * It contains the symbols used to format numbers, e.g. the grouping separator, + * decimal separator, and so on. + * + * @serial + * @see #setDecimalFormatSymbols + * @see java.text.DecimalFormatSymbols + */ + private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols(); + + /** + * True to force the use of exponential (i.e. scientific) notation when formatting + * numbers. + * + * @serial + * @since 1.2 + */ + private boolean useExponentialNotation; // Newly persistent in the Java 2 platform v.1.2 + + /** + * FieldPositions describing the positive prefix String. This is + * lazily created. Use getPositivePrefixFieldPositions + * when needed. + */ + private transient FieldPosition[] positivePrefixFieldPositions; + + /** + * FieldPositions describing the positive suffix String. This is + * lazily created. Use getPositiveSuffixFieldPositions + * when needed. + */ + private transient FieldPosition[] positiveSuffixFieldPositions; + + /** + * FieldPositions describing the negative prefix String. This is + * lazily created. Use getNegativePrefixFieldPositions + * when needed. + */ + private transient FieldPosition[] negativePrefixFieldPositions; + + /** + * FieldPositions describing the negative suffix String. This is + * lazily created. Use getNegativeSuffixFieldPositions + * when needed. + */ + private transient FieldPosition[] negativeSuffixFieldPositions; + + /** + * The minimum number of digits used to display the exponent when a number is + * formatted in exponential notation. This field is ignored if + * useExponentialNotation is not true. + * + * @serial + * @since 1.2 + */ + private byte minExponentDigits; // Newly persistent in the Java 2 platform v.1.2 + + /** + * The maximum number of digits allowed in the integer portion of a + * BigInteger or BigDecimal number. + * maximumIntegerDigits must be greater than or equal to + * minimumIntegerDigits. + * + * @serial + * @see #getMaximumIntegerDigits + * @since 1.5 + */ + private int maximumIntegerDigits = super.getMaximumIntegerDigits(); + + /** + * The minimum number of digits allowed in the integer portion of a + * BigInteger or BigDecimal number. + * minimumIntegerDigits must be less than or equal to + * maximumIntegerDigits. + * + * @serial + * @see #getMinimumIntegerDigits + * @since 1.5 + */ + private int minimumIntegerDigits = super.getMinimumIntegerDigits(); + + /** + * The maximum number of digits allowed in the fractional portion of a + * BigInteger or BigDecimal number. + * maximumFractionDigits must be greater than or equal to + * minimumFractionDigits. + * + * @serial + * @see #getMaximumFractionDigits + * @since 1.5 + */ + private int maximumFractionDigits = super.getMaximumFractionDigits(); + + /** + * The minimum number of digits allowed in the fractional portion of a + * BigInteger or BigDecimal number. + * minimumFractionDigits must be less than or equal to + * maximumFractionDigits. + * + * @serial + * @see #getMinimumFractionDigits + * @since 1.5 + */ + private int minimumFractionDigits = super.getMinimumFractionDigits(); + + /** + * The {@link java.math.RoundingMode} used in this DecimalFormat. + * + * @serial + * @since 1.6 + */ + private RoundingMode roundingMode = RoundingMode.HALF_EVEN; + + // ------ DecimalFormat fields for fast-path for double algorithm ------ + + /** + * Helper inner utility class for storing the data used in the fast-path + * algorithm. Almost all fields related to fast-path are encapsulated in + * this class. + * + * Any {@code DecimalFormat} instance has a {@code fastPathData} + * reference field that is null unless both the properties of the instance + * are such that the instance is in the "fast-path" state, and a format call + * has been done at least once while in this state. + * + * Almost all fields are related to the "fast-path" state only and don't + * change until one of the instance properties is changed. + * + * {@code firstUsedIndex} and {@code lastFreeIndex} are the only + * two fields that are used and modified while inside a call to + * {@code fastDoubleFormat}. + * + */ + private static class FastPathData { + // --- Temporary fields used in fast-path, shared by several methods. + + /** The first unused index at the end of the formatted result. */ + int lastFreeIndex; + + /** The first used index at the beginning of the formatted result */ + int firstUsedIndex; + + // --- State fields related to fast-path status. Changes due to a + // property change only. Set by checkAndSetFastPathStatus() only. + + /** Difference between locale zero and default zero representation. */ + int zeroDelta; + + /** Locale char for grouping separator. */ + char groupingChar; + + /** Fixed index position of last integral digit of formatted result */ + int integralLastIndex; + + /** Fixed index position of first fractional digit of formatted result */ + int fractionalFirstIndex; + + /** Fractional constants depending on decimal|currency state */ + double fractionalScaleFactor; + int fractionalMaxIntBound; + + + /** The char array buffer that will contain the formatted result */ + char[] fastPathContainer; + + /** Suffixes recorded as char array for efficiency. */ + char[] charsPositivePrefix; + char[] charsNegativePrefix; + char[] charsPositiveSuffix; + char[] charsNegativeSuffix; + boolean positiveAffixesRequired = true; + boolean negativeAffixesRequired = true; + } + + /** The format fast-path status of the instance. Logical state. */ + private transient boolean isFastPath = false; + + /** Flag stating need of check and reinit fast-path status on next format call. */ + private transient boolean fastPathCheckNeeded = true; + + /** DecimalFormat reference to its FastPathData */ + private transient FastPathData fastPathData; + + + //---------------------------------------------------------------------- + + static final int currentSerialVersion = 4; + + /** + * The internal serial version which says which version was written. + * Possible values are: + *

    + *
  • 0 (default): versions before the Java 2 platform v1.2 + *
  • 1: version for 1.2, which includes the two new fields + * useExponentialNotation and + * minExponentDigits. + *
  • 2: version for 1.3 and later, which adds four new fields: + * posPrefixPattern, posSuffixPattern, + * negPrefixPattern, and negSuffixPattern. + *
  • 3: version for 1.5 and later, which adds five new fields: + * maximumIntegerDigits, + * minimumIntegerDigits, + * maximumFractionDigits, + * minimumFractionDigits, and + * parseBigDecimal. + *
  • 4: version for 1.6 and later, which adds one new field: + * roundingMode. + *
+ * @since 1.2 + * @serial + */ + private int serialVersionOnStream = currentSerialVersion; + + //---------------------------------------------------------------------- + // CONSTANTS + //---------------------------------------------------------------------- + + // ------ Fast-Path for double Constants ------ + + /** Maximum valid integer value for applying fast-path algorithm */ + private static final double MAX_INT_AS_DOUBLE = (double) Integer.MAX_VALUE; + + /** + * The digit arrays used in the fast-path methods for collecting digits. + * Using 3 constants arrays of chars ensures a very fast collection of digits + */ + private static class DigitArrays { + static final char[] DigitOnes1000 = new char[1000]; + static final char[] DigitTens1000 = new char[1000]; + static final char[] DigitHundreds1000 = new char[1000]; + + // initialize on demand holder class idiom for arrays of digits + static { + int tenIndex = 0; + int hundredIndex = 0; + char digitOne = '0'; + char digitTen = '0'; + char digitHundred = '0'; + for (int i = 0; i < 1000; i++ ) { + + DigitOnes1000[i] = digitOne; + if (digitOne == '9') + digitOne = '0'; + else + digitOne++; + + DigitTens1000[i] = digitTen; + if (i == (tenIndex + 9)) { + tenIndex += 10; + if (digitTen == '9') + digitTen = '0'; + else + digitTen++; + } + + DigitHundreds1000[i] = digitHundred; + if (i == (hundredIndex + 99)) { + digitHundred++; + hundredIndex += 100; + } + } + } + } + // ------ Fast-Path for double Constants end ------ + + // Constants for characters used in programmatic (unlocalized) patterns. + private static final char PATTERN_ZERO_DIGIT = '0'; + private static final char PATTERN_GROUPING_SEPARATOR = ','; + private static final char PATTERN_DECIMAL_SEPARATOR = '.'; + private static final char PATTERN_PER_MILLE = '\u2030'; + private static final char PATTERN_PERCENT = '%'; + private static final char PATTERN_DIGIT = '#'; + private static final char PATTERN_SEPARATOR = ';'; + private static final String PATTERN_EXPONENT = "E"; + private static final char PATTERN_MINUS = '-'; + + /** + * The CURRENCY_SIGN is the standard Unicode symbol for currency. It + * is used in patterns and substituted with either the currency symbol, + * or if it is doubled, with the international currency symbol. If the + * CURRENCY_SIGN is seen in a pattern, then the decimal separator is + * replaced with the monetary decimal separator. + * + * The CURRENCY_SIGN is not localized. + */ + private static final char CURRENCY_SIGN = '\u00A4'; + + private static final char QUOTE = '\''; + + private static FieldPosition[] EmptyFieldPositionArray = new FieldPosition[0]; + + // Upper limit on integer and fraction digits for a Java double + static final int DOUBLE_INTEGER_DIGITS = 309; + static final int DOUBLE_FRACTION_DIGITS = 340; + + // Upper limit on integer and fraction digits for BigDecimal and BigInteger + static final int MAXIMUM_INTEGER_DIGITS = Integer.MAX_VALUE; + static final int MAXIMUM_FRACTION_DIGITS = Integer.MAX_VALUE; + + // Proclaim JDK 1.1 serial compatibility. + static final long serialVersionUID = 864413376551465018L; +} diff --git a/src/main/java/com/moparisthebest/text/DigitList.java b/src/main/java/com/moparisthebest/text/DigitList.java new file mode 100644 index 0000000..a355944 --- /dev/null +++ b/src/main/java/com/moparisthebest/text/DigitList.java @@ -0,0 +1,715 @@ +/* + * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +/** + * Digit List. Private to DecimalFormat. + * Handles the transcoding + * between numeric values and strings of characters. Only handles + * non-negative numbers. The division of labor between DigitList and + * DecimalFormat is that DigitList handles the radix 10 representation + * issues; DecimalFormat handles the locale-specific issues such as + * positive/negative, grouping, decimal point, currency, and so on. + * + * A DigitList is really a representation of a floating point value. + * It may be an integer value; we assume that a double has sufficient + * precision to represent all digits of a long. + * + * The DigitList representation consists of a string of characters, + * which are the digits radix 10, from '0' to '9'. It also has a radix + * 10 exponent associated with it. The value represented by a DigitList + * object can be computed by mulitplying the fraction f, where 0 <= f < 1, + * derived by placing all the digits of the list to the right of the + * decimal point, by 10^exponent. + * + * @see Locale + * @see Format + * @see NumberFormat + * @see DecimalFormat + * @see ChoiceFormat + * @see MessageFormat + * @author Mark Davis, Alan Liu + */ +final class DigitList implements Cloneable { + /** + * The maximum number of significant digits in an IEEE 754 double, that + * is, in a Java double. This must not be increased, or garbage digits + * will be generated, and should not be decreased, or accuracy will be lost. + */ + public static final int MAX_COUNT = 19; // == Long.toString(Long.MAX_VALUE).length() + + /** + * These data members are intentionally public and can be set directly. + * + * The value represented is given by placing the decimal point before + * digits[decimalAt]. If decimalAt is < 0, then leading zeros between + * the decimal point and the first nonzero digit are implied. If decimalAt + * is > count, then trailing zeros between the digits[count-1] and the + * decimal point are implied. + * + * Equivalently, the represented value is given by f * 10^decimalAt. Here + * f is a value 0.1 <= f < 1 arrived at by placing the digits in Digits to + * the right of the decimal. + * + * DigitList is normalized, so if it is non-zero, figits[0] is non-zero. We + * don't allow denormalized numbers because our exponent is effectively of + * unlimited magnitude. The count value contains the number of significant + * digits present in digits[]. + * + * Zero is represented by any DigitList with count == 0 or with each digits[i] + * for all i <= count == '0'. + */ + public int decimalAt = 0; + public int count = 0; + public char[] digits = new char[MAX_COUNT]; + + private char[] data; + private RoundingMode roundingMode = RoundingMode.HALF_EVEN; + private boolean isNegative = false; + + /** + * Return true if the represented number is zero. + */ + boolean isZero() { + for (int i=0; i < count; ++i) { + if (digits[i] != '0') { + return false; + } + } + return true; + } + + /** + * Set the rounding mode + */ + void setRoundingMode(RoundingMode r) { + roundingMode = r; + } + + /** + * Clears out the digits. + * Use before appending them. + * Typically, you set a series of digits with append, then at the point + * you hit the decimal point, you set myDigitList.decimalAt = myDigitList.count; + * then go on appending digits. + */ + public void clear () { + decimalAt = 0; + count = 0; + } + + /** + * Appends a digit to the list, extending the list when necessary. + */ + public void append(char digit) { + if (count == digits.length) { + char[] data = new char[count + 100]; + System.arraycopy(digits, 0, data, 0, count); + digits = data; + } + digits[count++] = digit; + } + + /** + * Utility routine to get the value of the digit list + * If (count == 0) this throws a NumberFormatException, which + * mimics Long.parseLong(). + */ + public final double getDouble() { + if (count == 0) { + return 0.0; + } + + StringBuffer temp = getStringBuffer(); + temp.append('.'); + temp.append(digits, 0, count); + temp.append('E'); + temp.append(decimalAt); + return Double.parseDouble(temp.toString()); + } + + /** + * Utility routine to get the value of the digit list. + * If (count == 0) this returns 0, unlike Long.parseLong(). + */ + public final long getLong() { + // for now, simple implementation; later, do proper IEEE native stuff + + if (count == 0) { + return 0; + } + + // We have to check for this, because this is the one NEGATIVE value + // we represent. If we tried to just pass the digits off to parseLong, + // we'd get a parse failure. + if (isLongMIN_VALUE()) { + return Long.MIN_VALUE; + } + + StringBuffer temp = getStringBuffer(); + temp.append(digits, 0, count); + for (int i = count; i < decimalAt; ++i) { + temp.append('0'); + } + return Long.parseLong(temp.toString()); + } + + public final BigDecimal getBigDecimal() { + if (count == 0) { + if (decimalAt == 0) { + return BigDecimal.ZERO; + } else { + return new BigDecimal("0E" + decimalAt); + } + } + + if (decimalAt == count) { + return new BigDecimal(digits, 0, count); + } else { + return new BigDecimal(digits, 0, count).scaleByPowerOfTen(decimalAt - count); + } + } + + /** + * Return true if the number represented by this object can fit into + * a long. + * @param isPositive true if this number should be regarded as positive + * @param ignoreNegativeZero true if -0 should be regarded as identical to + * +0; otherwise they are considered distinct + * @return true if this number fits into a Java long + */ + boolean fitsIntoLong(boolean isPositive, boolean ignoreNegativeZero) { + // Figure out if the result will fit in a long. We have to + // first look for nonzero digits after the decimal point; + // then check the size. If the digit count is 18 or less, then + // the value can definitely be represented as a long. If it is 19 + // then it may be too large. + + // Trim trailing zeros. This does not change the represented value. + while (count > 0 && digits[count - 1] == '0') { + --count; + } + + if (count == 0) { + // Positive zero fits into a long, but negative zero can only + // be represented as a double. - bug 4162852 + return isPositive || ignoreNegativeZero; + } + + if (decimalAt < count || decimalAt > MAX_COUNT) { + return false; + } + + if (decimalAt < MAX_COUNT) return true; + + // At this point we have decimalAt == count, and count == MAX_COUNT. + // The number will overflow if it is larger than 9223372036854775807 + // or smaller than -9223372036854775808. + for (int i=0; i max) return false; + if (dig < max) return true; + } + + // At this point the first count digits match. If decimalAt is less + // than count, then the remaining digits are zero, and we return true. + if (count < decimalAt) return true; + + // Now we have a representation of Long.MIN_VALUE, without the leading + // negative sign. If this represents a positive value, then it does + // not fit; otherwise it fits. + return !isPositive; + } + + /** + * Set the digit list to a representation of the given double value. + * This method supports fixed-point notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be Inf, -Inf, Nan, + * or a value <= 0. + * @param maximumFractionDigits The most fractional digits which should + * be converted. + */ + public final void set(boolean isNegative, double source, int maximumFractionDigits) { + set(isNegative, source, maximumFractionDigits, true); + } + + /** + * Set the digit list to a representation of the given double value. + * This method supports both fixed-point and exponential notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be Inf, -Inf, Nan, + * or a value <= 0. + * @param maximumDigits The most fractional or total digits which should + * be converted. + * @param fixedPoint If true, then maximumDigits is the maximum + * fractional digits to be converted. If false, total digits. + */ + final void set(boolean isNegative, double source, int maximumDigits, boolean fixedPoint) { + set(isNegative, Double.toString(source), maximumDigits, fixedPoint); + } + + /** + * Generate a representation of the form DDDDD, DDDDD.DDDDD, or + * DDDDDE+/-DDDDD. + */ + final void set(boolean isNegative, String s, int maximumDigits, boolean fixedPoint) { + this.isNegative = isNegative; + int len = s.length(); + char[] source = getDataChars(len); + s.getChars(0, len, source, 0); + + decimalAt = -1; + count = 0; + int exponent = 0; + // Number of zeros between decimal point and first non-zero digit after + // decimal point, for numbers < 1. + int leadingZerosAfterDecimal = 0; + boolean nonZeroDigitSeen = false; + + for (int i = 0; i < len; ) { + char c = source[i++]; + if (c == '.') { + decimalAt = count; + } else if (c == 'e' || c == 'E') { + exponent = parseInt(source, i, len); + break; + } else { + if (!nonZeroDigitSeen) { + nonZeroDigitSeen = (c != '0'); + if (!nonZeroDigitSeen && decimalAt != -1) + ++leadingZerosAfterDecimal; + } + if (nonZeroDigitSeen) { + digits[count++] = c; + } + } + } + if (decimalAt == -1) { + decimalAt = count; + } + if (nonZeroDigitSeen) { + decimalAt += exponent - leadingZerosAfterDecimal; + } + + if (fixedPoint) { + // The negative of the exponent represents the number of leading + // zeros between the decimal and the first non-zero digit, for + // a value < 0.1 (e.g., for 0.00123, -decimalAt == 2). If this + // is more than the maximum fraction digits, then we have an underflow + // for the printed representation. + if (-decimalAt > maximumDigits) { + // Handle an underflow to zero when we round something like + // 0.0009 to 2 fractional digits. + count = 0; + return; + } else if (-decimalAt == maximumDigits) { + // If we round 0.0009 to 3 fractional digits, then we have to + // create a new one digit in the least significant location. + if (shouldRoundUp(0)) { + count = 1; + ++decimalAt; + digits[0] = '1'; + } else { + count = 0; + } + return; + } + // else fall through + } + + // Eliminate trailing zeros. + while (count > 1 && digits[count - 1] == '0') { + --count; + } + + // Eliminate digits beyond maximum digits to be displayed. + // Round up if appropriate. + round(fixedPoint ? (maximumDigits + decimalAt) : maximumDigits); + } + + /** + * Round the representation to the given number of digits. + * @param maximumDigits The maximum number of digits to be shown. + * Upon return, count will be less than or equal to maximumDigits. + */ + private final void round(int maximumDigits) { + // Eliminate digits beyond maximum digits to be displayed. + // Round up if appropriate. + if (maximumDigits >= 0 && maximumDigits < count) { + if (shouldRoundUp(maximumDigits)) { + // Rounding up involved incrementing digits from LSD to MSD. + // In most cases this is simple, but in a worst case situation + // (9999..99) we have to adjust the decimalAt value. + for (;;) { + --maximumDigits; + if (maximumDigits < 0) { + // We have all 9's, so we increment to a single digit + // of one and adjust the exponent. + digits[0] = '1'; + ++decimalAt; + maximumDigits = 0; // Adjust the count + break; + } + + ++digits[maximumDigits]; + if (digits[maximumDigits] <= '9') break; + // digits[maximumDigits] = '0'; // Unnecessary since we'll truncate this + } + ++maximumDigits; // Increment for use as count + } + count = maximumDigits; + + // Eliminate trailing zeros. + while (count > 1 && digits[count-1] == '0') { + --count; + } + } + } + + + /** + * Return true if truncating the representation to the given number + * of digits will result in an increment to the last digit. This + * method implements the rounding modes defined in the + * java.math.RoundingMode class. + * [bnf] + * @param maximumDigits the number of digits to keep, from 0 to + * count-1. If 0, then all digits are rounded away, and + * this method returns true if a one should be generated (e.g., formatting + * 0.09 with "#.#"). + * @exception ArithmeticException if rounding is needed with rounding + * mode being set to RoundingMode.UNNECESSARY + * @return true if digit maximumDigits-1 should be + * incremented + */ + private boolean shouldRoundUp(int maximumDigits) { + if (maximumDigits < count) { + switch(roundingMode) { + case UP: + for (int i=maximumDigits; i= '5') { + return true; + } + break; + case HALF_DOWN: + if (digits[maximumDigits] > '5') { + return true; + } else if (digits[maximumDigits] == '5' ) { + for (int i=maximumDigits+1; i '5') { + return true; + } else if (digits[maximumDigits] == '5' ) { + for (int i=maximumDigits+1; i 0 && (digits[maximumDigits-1] % 2 != 0); + } + break; + case UNNECESSARY: + for (int i=maximumDigits; i= 0 or == + * Long.MIN_VALUE. + * @param maximumDigits The most digits which should be converted. + * If maximumDigits is lower than the number of significant digits + * in source, the representation will be rounded. Ignored if <= 0. + */ + public final void set(boolean isNegative, long source, int maximumDigits) { + this.isNegative = isNegative; + + // This method does not expect a negative number. However, + // "source" can be a Long.MIN_VALUE (-9223372036854775808), + // if the number being formatted is a Long.MIN_VALUE. In that + // case, it will be formatted as -Long.MIN_VALUE, a number + // which is outside the legal range of a long, but which can + // be represented by DigitList. + if (source <= 0) { + if (source == Long.MIN_VALUE) { + decimalAt = count = MAX_COUNT; + System.arraycopy(LONG_MIN_REP, 0, digits, 0, count); + } else { + decimalAt = count = 0; // Values <= 0 format as zero + } + } else { + // Rewritten to improve performance. I used to call + // Long.toString(), which was about 4x slower than this code. + int left = MAX_COUNT; + int right; + while (source > 0) { + digits[--left] = (char)('0' + (source % 10)); + source /= 10; + } + decimalAt = MAX_COUNT - left; + // Don't copy trailing zeros. We are guaranteed that there is at + // least one non-zero digit, so we don't have to check lower bounds. + for (right = MAX_COUNT - 1; digits[right] == '0'; --right) + ; + count = right - left + 1; + System.arraycopy(digits, left, digits, 0, count); + } + if (maximumDigits > 0) round(maximumDigits); + } + + /** + * Set the digit list to a representation of the given BigDecimal value. + * This method supports both fixed-point and exponential notation. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must not be a value <= 0. + * @param maximumDigits The most fractional or total digits which should + * be converted. + * @param fixedPoint If true, then maximumDigits is the maximum + * fractional digits to be converted. If false, total digits. + */ + final void set(boolean isNegative, BigDecimal source, int maximumDigits, boolean fixedPoint) { + String s = source.toString(); + extendDigits(s.length()); + + set(isNegative, s, maximumDigits, fixedPoint); + } + + /** + * Set the digit list to a representation of the given BigInteger value. + * @param isNegative Boolean value indicating whether the number is negative. + * @param source Value to be converted; must be >= 0. + * @param maximumDigits The most digits which should be converted. + * If maximumDigits is lower than the number of significant digits + * in source, the representation will be rounded. Ignored if <= 0. + */ + final void set(boolean isNegative, BigInteger source, int maximumDigits) { + this.isNegative = isNegative; + String s = source.toString(); + int len = s.length(); + extendDigits(len); + s.getChars(0, len, digits, 0); + + decimalAt = len; + int right; + for (right = len - 1; right >= 0 && digits[right] == '0'; --right) + ; + count = right + 1; + + if (maximumDigits > 0) { + round(maximumDigits); + } + } + + /** + * equality test between two digit lists. + */ + public boolean equals(Object obj) { + if (this == obj) // quick check + return true; + if (!(obj instanceof DigitList)) // (1) same object? + return false; + DigitList other = (DigitList) obj; + if (count != other.count || + decimalAt != other.decimalAt) + return false; + for (int i = 0; i < count; i++) + if (digits[i] != other.digits[i]) + return false; + return true; + } + + /** + * Generates the hash code for the digit list. + */ + public int hashCode() { + int hashcode = decimalAt; + + for (int i = 0; i < count; i++) { + hashcode = hashcode * 37 + digits[i]; + } + + return hashcode; + } + + /** + * Creates a copy of this object. + * @return a clone of this instance. + */ + public Object clone() { + try { + DigitList other = (DigitList) super.clone(); + char[] newDigits = new char[digits.length]; + System.arraycopy(digits, 0, newDigits, 0, digits.length); + other.digits = newDigits; + other.tempBuffer = null; + return other; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Returns true if this DigitList represents Long.MIN_VALUE; + * false, otherwise. This is required so that getLong() works. + */ + private boolean isLongMIN_VALUE() { + if (decimalAt != count || count != MAX_COUNT) { + return false; + } + + for (int i = 0; i < count; ++i) { + if (digits[i] != LONG_MIN_REP[i]) return false; + } + + return true; + } + + private static final int parseInt(char[] str, int offset, int strLen) { + char c; + boolean positive = true; + if ((c = str[offset]) == '-') { + positive = false; + offset++; + } else if (c == '+') { + offset++; + } + + int value = 0; + while (offset < strLen) { + c = str[offset++]; + if (c >= '0' && c <= '9') { + value = value * 10 + (c - '0'); + } else { + break; + } + } + return positive ? value : -value; + } + + // The digit part of -9223372036854775808L + private static final char[] LONG_MIN_REP = "9223372036854775808".toCharArray(); + + public String toString() { + if (isZero()) { + return "0"; + } + StringBuffer buf = getStringBuffer(); + buf.append("0."); + buf.append(digits, 0, count); + buf.append("x10^"); + buf.append(decimalAt); + return buf.toString(); + } + + private StringBuffer tempBuffer; + + private StringBuffer getStringBuffer() { + if (tempBuffer == null) { + tempBuffer = new StringBuffer(MAX_COUNT); + } else { + tempBuffer.setLength(0); + } + return tempBuffer; + } + + private void extendDigits(int len) { + if (len > digits.length) { + digits = new char[len]; + } + } + + private final char[] getDataChars(int length) { + if (data == null || data.length < length) { + data = new char[length]; + } + return data; + } +} diff --git a/src/main/java/com/moparisthebest/text/FieldDelegate.java b/src/main/java/com/moparisthebest/text/FieldDelegate.java new file mode 100644 index 0000000..9f639ec --- /dev/null +++ b/src/main/java/com/moparisthebest/text/FieldDelegate.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved + * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved + * + * The original version of this source code and documentation is copyrighted + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These + * materials are provided under terms of a License Agreement between Taligent + * and Sun. This technology is protected by multiple US and International + * patents. This notice and attribution to Taligent may not be removed. + * Taligent is a registered trademark of Taligent, Inc. + * + */ + +package java.text; + +/** + * FieldDelegate is notified by the various Format + * implementations as they are formatting the Objects. This allows for + * storage of the individual sections of the formatted String for + * later use, such as in a FieldPosition or for an + * AttributedCharacterIterator. + *

+ * Delegates should NOT assume that the Format will notify + * the delegate of fields in any particular order. + * + * @see FieldPosition#getFieldDelegate + * @see CharacterIteratorFieldDelegate + */ +interface FieldDelegate { + /** + * Notified when a particular region of the String is formatted. This + * method will be invoked if there is no corresponding integer field id + * matching attr. + * + * @param attr Identifies the field matched + * @param value Value associated with the field + * @param start Beginning location of the field, will be >= 0 + * @param end End of the field, will be >= start and <= buffer.length() + * @param buffer Contains current formatted value, receiver should + * NOT modify it. + */ + public void formatted(Format.Field attr, Object value, int start, + int end, StringBuffer buffer); + + /** + * Notified when a particular region of the String is formatted. + * + * @param fieldID Identifies the field by integer + * @param attr Identifies the field matched + * @param value Value associated with the field + * @param start Beginning location of the field, will be >= 0 + * @param end End of the field, will be >= start and <= buffer.length() + * @param buffer Contains current formatted value, receiver should + * NOT modify it. + */ + public void formatted(int fieldID, Format.Field attr, Object value, + int start, int end, StringBuffer buffer); +} diff --git a/src/main/java/com/moparisthebest/text/OldDecimalFormat.java b/src/main/java/com/moparisthebest/text/OldDecimalFormat.java new file mode 100644 index 0000000..ecc922f --- /dev/null +++ b/src/main/java/com/moparisthebest/text/OldDecimalFormat.java @@ -0,0 +1,65 @@ +/* + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +package com.moparisthebest.text; + +import java.text.DecimalFormatSymbols; + +/** + * This class can be used if you don't want to import two + * classes named the same, it simply extends + * com.moparisthebest.text.DecimalFormat + * and changes no behavior. + */ +public class OldDecimalFormat extends DecimalFormat { + + public static void main(String[] args) { + final String num1 = args.length > 0 ? args[0] : "50"; + final String num2 = args.length > 1 ? args[1] : "0.0259"; + final int precision = Integer.parseInt(args.length > 2 ? args[2] : "2"); + + System.out.printf("%s * %s with precision of %d:%n", num1, num2, precision); + + final java.math.BigDecimal bd = new java.math.BigDecimal(num1).multiply(new java.math.BigDecimal(num2)); + System.out.println("java.math.BigDecimal: " + bd.setScale(precision, java.math.RoundingMode.HALF_EVEN).toString()); + + final double value = Double.parseDouble(num1) * Double.parseDouble(num2); + + java.text.DecimalFormat df = new com.moparisthebest.text.DecimalFormat(); + df.setMaximumFractionDigits(precision); + df.setMinimumFractionDigits(precision); + System.out.println("com.moparisthebest.text.DecimalFormat: " + df.format(value)); + + df = new java.text.DecimalFormat(); + df.setMaximumFractionDigits(precision); + df.setMinimumFractionDigits(precision); + System.out.println("java.text.DecimalFormat: " + df.format(value)); + } + + public OldDecimalFormat() { + super(); + } + + public OldDecimalFormat(String pattern) { + super(pattern); + } + + public OldDecimalFormat(String pattern, DecimalFormatSymbols symbols) { + super(pattern, symbols); + } + +}