Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/text/ChoiceFormat.java
38829 views
/*1* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved27* (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved28*29* The original version of this source code and documentation is copyrighted30* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These31* materials are provided under terms of a License Agreement between Taligent32* and Sun. This technology is protected by multiple US and International33* patents. This notice and attribution to Taligent may not be removed.34* Taligent is a registered trademark of Taligent, Inc.35*36*/3738package java.text;3940import java.io.InvalidObjectException;41import java.io.IOException;42import java.io.ObjectInputStream;43import java.util.Arrays;4445/**46* A <code>ChoiceFormat</code> allows you to attach a format to a range of numbers.47* It is generally used in a <code>MessageFormat</code> for handling plurals.48* The choice is specified with an ascending list of doubles, where each item49* specifies a half-open interval up to the next item:50* <blockquote>51* <pre>52* X matches j if and only if limit[j] ≤ X < limit[j+1]53* </pre>54* </blockquote>55* If there is no match, then either the first or last index is used, depending56* on whether the number (X) is too low or too high. If the limit array is not57* in ascending order, the results of formatting will be incorrect. ChoiceFormat58* also accepts <code>\u221E</code> as equivalent to infinity(INF).59*60* <p>61* <strong>Note:</strong>62* <code>ChoiceFormat</code> differs from the other <code>Format</code>63* classes in that you create a <code>ChoiceFormat</code> object with a64* constructor (not with a <code>getInstance</code> style factory65* method). The factory methods aren't necessary because <code>ChoiceFormat</code>66* doesn't require any complex setup for a given locale. In fact,67* <code>ChoiceFormat</code> doesn't implement any locale specific behavior.68*69* <p>70* When creating a <code>ChoiceFormat</code>, you must specify an array of formats71* and an array of limits. The length of these arrays must be the same.72* For example,73* <ul>74* <li>75* <em>limits</em> = {1,2,3,4,5,6,7}<br>76* <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}77* <li>78* <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br>79* <em>formats</em> = {"no files", "one file", "many files"}<br>80* (<code>nextDouble</code> can be used to get the next higher double, to81* make the half-open interval.)82* </ul>83*84* <p>85* Here is a simple example that shows formatting and parsing:86* <blockquote>87* <pre>{@code88* double[] limits = {1,2,3,4,5,6,7};89* String[] dayOfWeekNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"};90* ChoiceFormat form = new ChoiceFormat(limits, dayOfWeekNames);91* ParsePosition status = new ParsePosition(0);92* for (double i = 0.0; i <= 8.0; ++i) {93* status.setIndex(0);94* System.out.println(i + " -> " + form.format(i) + " -> "95* + form.parse(form.format(i),status));96* }97* }</pre>98* </blockquote>99* Here is a more complex example, with a pattern format:100* <blockquote>101* <pre>{@code102* double[] filelimits = {0,1,2};103* String[] filepart = {"are no files","is one file","are {2} files"};104* ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);105* Format[] testFormats = {fileform, null, NumberFormat.getInstance()};106* MessageFormat pattform = new MessageFormat("There {0} on {1}");107* pattform.setFormats(testFormats);108* Object[] testArgs = {null, "ADisk", null};109* for (int i = 0; i < 4; ++i) {110* testArgs[0] = new Integer(i);111* testArgs[2] = testArgs[0];112* System.out.println(pattform.format(testArgs));113* }114* }</pre>115* </blockquote>116* <p>117* Specifying a pattern for ChoiceFormat objects is fairly straightforward.118* For example:119* <blockquote>120* <pre>{@code121* ChoiceFormat fmt = new ChoiceFormat(122* "-1#is negative| 0#is zero or fraction | 1#is one |1.0<is 1+ |2#is two |2<is more than 2.");123* System.out.println("Formatter Pattern : " + fmt.toPattern());124*125* System.out.println("Format with -INF : " + fmt.format(Double.NEGATIVE_INFINITY));126* System.out.println("Format with -1.0 : " + fmt.format(-1.0));127* System.out.println("Format with 0 : " + fmt.format(0));128* System.out.println("Format with 0.9 : " + fmt.format(0.9));129* System.out.println("Format with 1.0 : " + fmt.format(1));130* System.out.println("Format with 1.5 : " + fmt.format(1.5));131* System.out.println("Format with 2 : " + fmt.format(2));132* System.out.println("Format with 2.1 : " + fmt.format(2.1));133* System.out.println("Format with NaN : " + fmt.format(Double.NaN));134* System.out.println("Format with +INF : " + fmt.format(Double.POSITIVE_INFINITY));135* }</pre>136* </blockquote>137* And the output result would be like the following:138* <blockquote>139* <pre>{@code140* Format with -INF : is negative141* Format with -1.0 : is negative142* Format with 0 : is zero or fraction143* Format with 0.9 : is zero or fraction144* Format with 1.0 : is one145* Format with 1.5 : is 1+146* Format with 2 : is two147* Format with 2.1 : is more than 2.148* Format with NaN : is negative149* Format with +INF : is more than 2.150* }</pre>151* </blockquote>152*153* <h3><a name="synchronization">Synchronization</a></h3>154*155* <p>156* Choice formats are not synchronized.157* It is recommended to create separate format instances for each thread.158* If multiple threads access a format concurrently, it must be synchronized159* externally.160*161*162* @see DecimalFormat163* @see MessageFormat164* @author Mark Davis165*/166public class ChoiceFormat extends NumberFormat {167168// Proclaim serial compatibility with 1.1 FCS169private static final long serialVersionUID = 1795184449645032964L;170171/**172* Sets the pattern.173* @param newPattern See the class description.174*/175public void applyPattern(String newPattern) {176StringBuffer[] segments = new StringBuffer[2];177for (int i = 0; i < segments.length; ++i) {178segments[i] = new StringBuffer();179}180double[] newChoiceLimits = new double[30];181String[] newChoiceFormats = new String[30];182int count = 0;183int part = 0;184double startValue = 0;185double oldStartValue = Double.NaN;186boolean inQuote = false;187for (int i = 0; i < newPattern.length(); ++i) {188char ch = newPattern.charAt(i);189if (ch=='\'') {190// Check for "''" indicating a literal quote191if ((i+1)<newPattern.length() && newPattern.charAt(i+1)==ch) {192segments[part].append(ch);193++i;194} else {195inQuote = !inQuote;196}197} else if (inQuote) {198segments[part].append(ch);199} else if (ch == '<' || ch == '#' || ch == '\u2264') {200if (segments[0].length() == 0) {201throw new IllegalArgumentException();202}203try {204String tempBuffer = segments[0].toString();205if (tempBuffer.equals("\u221E")) {206startValue = Double.POSITIVE_INFINITY;207} else if (tempBuffer.equals("-\u221E")) {208startValue = Double.NEGATIVE_INFINITY;209} else {210startValue = Double.valueOf(segments[0].toString()).doubleValue();211}212} catch (Exception e) {213throw new IllegalArgumentException();214}215if (ch == '<' && startValue != Double.POSITIVE_INFINITY &&216startValue != Double.NEGATIVE_INFINITY) {217startValue = nextDouble(startValue);218}219if (startValue <= oldStartValue) {220throw new IllegalArgumentException();221}222segments[0].setLength(0);223part = 1;224} else if (ch == '|') {225if (count == newChoiceLimits.length) {226newChoiceLimits = doubleArraySize(newChoiceLimits);227newChoiceFormats = doubleArraySize(newChoiceFormats);228}229newChoiceLimits[count] = startValue;230newChoiceFormats[count] = segments[1].toString();231++count;232oldStartValue = startValue;233segments[1].setLength(0);234part = 0;235} else {236segments[part].append(ch);237}238}239// clean up last one240if (part == 1) {241if (count == newChoiceLimits.length) {242newChoiceLimits = doubleArraySize(newChoiceLimits);243newChoiceFormats = doubleArraySize(newChoiceFormats);244}245newChoiceLimits[count] = startValue;246newChoiceFormats[count] = segments[1].toString();247++count;248}249choiceLimits = new double[count];250System.arraycopy(newChoiceLimits, 0, choiceLimits, 0, count);251choiceFormats = new String[count];252System.arraycopy(newChoiceFormats, 0, choiceFormats, 0, count);253}254255/**256* Gets the pattern.257*258* @return the pattern string259*/260public String toPattern() {261StringBuffer result = new StringBuffer();262for (int i = 0; i < choiceLimits.length; ++i) {263if (i != 0) {264result.append('|');265}266// choose based upon which has less precision267// approximate that by choosing the closest one to an integer.268// could do better, but it's not worth it.269double less = previousDouble(choiceLimits[i]);270double tryLessOrEqual = Math.abs(Math.IEEEremainder(choiceLimits[i], 1.0d));271double tryLess = Math.abs(Math.IEEEremainder(less, 1.0d));272273if (tryLessOrEqual < tryLess) {274result.append(""+choiceLimits[i]);275result.append('#');276} else {277if (choiceLimits[i] == Double.POSITIVE_INFINITY) {278result.append("\u221E");279} else if (choiceLimits[i] == Double.NEGATIVE_INFINITY) {280result.append("-\u221E");281} else {282result.append(""+less);283}284result.append('<');285}286// Append choiceFormats[i], using quotes if there are special characters.287// Single quotes themselves must be escaped in either case.288String text = choiceFormats[i];289boolean needQuote = text.indexOf('<') >= 0290|| text.indexOf('#') >= 0291|| text.indexOf('\u2264') >= 0292|| text.indexOf('|') >= 0;293if (needQuote) result.append('\'');294if (text.indexOf('\'') < 0) result.append(text);295else {296for (int j=0; j<text.length(); ++j) {297char c = text.charAt(j);298result.append(c);299if (c == '\'') result.append(c);300}301}302if (needQuote) result.append('\'');303}304return result.toString();305}306307/**308* Constructs with limits and corresponding formats based on the pattern.309*310* @param newPattern the new pattern string311* @see #applyPattern312*/313public ChoiceFormat(String newPattern) {314applyPattern(newPattern);315}316317/**318* Constructs with the limits and the corresponding formats.319*320* @param limits limits in ascending order321* @param formats corresponding format strings322* @see #setChoices323*/324public ChoiceFormat(double[] limits, String[] formats) {325setChoices(limits, formats);326}327328/**329* Set the choices to be used in formatting.330* @param limits contains the top value that you want331* parsed with that format, and should be in ascending sorted order. When332* formatting X, the choice will be the i, where333* limit[i] ≤ X {@literal <} limit[i+1].334* If the limit array is not in ascending order, the results of formatting335* will be incorrect.336* @param formats are the formats you want to use for each limit.337* They can be either Format objects or Strings.338* When formatting with object Y,339* if the object is a NumberFormat, then ((NumberFormat) Y).format(X)340* is called. Otherwise Y.toString() is called.341*/342public void setChoices(double[] limits, String formats[]) {343if (limits.length != formats.length) {344throw new IllegalArgumentException(345"Array and limit arrays must be of the same length.");346}347choiceLimits = Arrays.copyOf(limits, limits.length);348choiceFormats = Arrays.copyOf(formats, formats.length);349}350351/**352* Get the limits passed in the constructor.353* @return the limits.354*/355public double[] getLimits() {356double[] newLimits = Arrays.copyOf(choiceLimits, choiceLimits.length);357return newLimits;358}359360/**361* Get the formats passed in the constructor.362* @return the formats.363*/364public Object[] getFormats() {365Object[] newFormats = Arrays.copyOf(choiceFormats, choiceFormats.length);366return newFormats;367}368369// Overrides370371/**372* Specialization of format. This method really calls373* <code>format(double, StringBuffer, FieldPosition)</code>374* thus the range of longs that are supported is only equal to375* the range that can be stored by double. This will never be376* a practical limitation.377*/378public StringBuffer format(long number, StringBuffer toAppendTo,379FieldPosition status) {380return format((double)number, toAppendTo, status);381}382383/**384* Returns pattern with formatted double.385* @param number number to be formatted and substituted.386* @param toAppendTo where text is appended.387* @param status ignore no useful status is returned.388*/389public StringBuffer format(double number, StringBuffer toAppendTo,390FieldPosition status) {391// find the number392int i;393for (i = 0; i < choiceLimits.length; ++i) {394if (!(number >= choiceLimits[i])) {395// same as number < choiceLimits, except catchs NaN396break;397}398}399--i;400if (i < 0) i = 0;401// return either a formatted number, or a string402return toAppendTo.append(choiceFormats[i]);403}404405/**406* Parses a Number from the input text.407* @param text the source text.408* @param status an input-output parameter. On input, the409* status.index field indicates the first character of the410* source text that should be parsed. On exit, if no error411* occurred, status.index is set to the first unparsed character412* in the source text. On exit, if an error did occur,413* status.index is unchanged and status.errorIndex is set to the414* first index of the character that caused the parse to fail.415* @return A Number representing the value of the number parsed.416*/417public Number parse(String text, ParsePosition status) {418// find the best number (defined as the one with the longest parse)419int start = status.index;420int furthest = start;421double bestNumber = Double.NaN;422double tempNumber = 0.0;423for (int i = 0; i < choiceFormats.length; ++i) {424String tempString = choiceFormats[i];425if (text.regionMatches(start, tempString, 0, tempString.length())) {426status.index = start + tempString.length();427tempNumber = choiceLimits[i];428if (status.index > furthest) {429furthest = status.index;430bestNumber = tempNumber;431if (furthest == text.length()) break;432}433}434}435status.index = furthest;436if (status.index == start) {437status.errorIndex = furthest;438}439return new Double(bestNumber);440}441442/**443* Finds the least double greater than {@code d}.444* If {@code NaN}, returns same value.445* <p>Used to make half-open intervals.446*447* @param d the reference value448* @return the least double value greather than {@code d}449* @see #previousDouble450*/451public static final double nextDouble (double d) {452return nextDouble(d,true);453}454455/**456* Finds the greatest double less than {@code d}.457* If {@code NaN}, returns same value.458*459* @param d the reference value460* @return the greatest double value less than {@code d}461* @see #nextDouble462*/463public static final double previousDouble (double d) {464return nextDouble(d,false);465}466467/**468* Overrides Cloneable469*/470public Object clone()471{472ChoiceFormat other = (ChoiceFormat) super.clone();473// for primitives or immutables, shallow clone is enough474other.choiceLimits = choiceLimits.clone();475other.choiceFormats = choiceFormats.clone();476return other;477}478479/**480* Generates a hash code for the message format object.481*/482public int hashCode() {483int result = choiceLimits.length;484if (choiceFormats.length > 0) {485// enough for reasonable distribution486result ^= choiceFormats[choiceFormats.length-1].hashCode();487}488return result;489}490491/**492* Equality comparision between two493*/494public boolean equals(Object obj) {495if (obj == null) return false;496if (this == obj) // quick check497return true;498if (getClass() != obj.getClass())499return false;500ChoiceFormat other = (ChoiceFormat) obj;501return (Arrays.equals(choiceLimits, other.choiceLimits)502&& Arrays.equals(choiceFormats, other.choiceFormats));503}504505/**506* After reading an object from the input stream, do a simple verification507* to maintain class invariants.508* @throws InvalidObjectException if the objects read from the stream is invalid.509*/510private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {511in.defaultReadObject();512if (choiceLimits.length != choiceFormats.length) {513throw new InvalidObjectException(514"limits and format arrays of different length.");515}516}517518// ===============privates===========================519520/**521* A list of lower bounds for the choices. The formatter will return522* <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to523* <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.524* @serial525*/526private double[] choiceLimits;527528/**529* A list of choice strings. The formatter will return530* <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to531* <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.532* @serial533*/534private String[] choiceFormats;535536/*537static final long SIGN = 0x8000000000000000L;538static final long EXPONENT = 0x7FF0000000000000L;539static final long SIGNIFICAND = 0x000FFFFFFFFFFFFFL;540541private static double nextDouble (double d, boolean positive) {542if (Double.isNaN(d) || Double.isInfinite(d)) {543return d;544}545long bits = Double.doubleToLongBits(d);546long significand = bits & SIGNIFICAND;547if (bits < 0) {548significand |= (SIGN | EXPONENT);549}550long exponent = bits & EXPONENT;551if (positive) {552significand += 1;553// FIXME fix overflow & underflow554} else {555significand -= 1;556// FIXME fix overflow & underflow557}558bits = exponent | (significand & ~EXPONENT);559return Double.longBitsToDouble(bits);560}561*/562563static final long SIGN = 0x8000000000000000L;564static final long EXPONENT = 0x7FF0000000000000L;565static final long POSITIVEINFINITY = 0x7FF0000000000000L;566567/**568* Finds the least double greater than {@code d} (if {@code positive} is569* {@code true}), or the greatest double less than {@code d} (if570* {@code positive} is {@code false}).571* If {@code NaN}, returns same value.572*573* Does not affect floating-point flags,574* provided these member functions do not:575* Double.longBitsToDouble(long)576* Double.doubleToLongBits(double)577* Double.isNaN(double)578*579* @param d the reference value580* @param positive {@code true} if the least double is desired;581* {@code false} otherwise582* @return the least or greater double value583*/584public static double nextDouble (double d, boolean positive) {585586/* filter out NaN's */587if (Double.isNaN(d)) {588return d;589}590591/* zero's are also a special case */592if (d == 0.0) {593double smallestPositiveDouble = Double.longBitsToDouble(1L);594if (positive) {595return smallestPositiveDouble;596} else {597return -smallestPositiveDouble;598}599}600601/* if entering here, d is a nonzero value */602603/* hold all bits in a long for later use */604long bits = Double.doubleToLongBits(d);605606/* strip off the sign bit */607long magnitude = bits & ~SIGN;608609/* if next double away from zero, increase magnitude */610if ((bits > 0) == positive) {611if (magnitude != POSITIVEINFINITY) {612magnitude += 1;613}614}615/* else decrease magnitude */616else {617magnitude -= 1;618}619620/* restore sign bit and return */621long signbit = bits & SIGN;622return Double.longBitsToDouble (magnitude | signbit);623}624625private static double[] doubleArraySize(double[] array) {626int oldSize = array.length;627double[] newArray = new double[oldSize * 2];628System.arraycopy(array, 0, newArray, 0, oldSize);629return newArray;630}631632private String[] doubleArraySize(String[] array) {633int oldSize = array.length;634String[] newArray = new String[oldSize * 2];635System.arraycopy(array, 0, newArray, 0, oldSize);636return newArray;637}638639}640641642