* Copyright (c) 1996, 2019, 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.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.text.spi.NumberFormatProvider;
import java.util.Currency;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleServiceProviderPool;
public abstract class NumberFormat extends Format {
public static final int INTEGER_FIELD = 0;
public static final int FRACTION_FIELD = 1;
protected NumberFormat() {
}
@Override
public 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 Number) {
return format(((Number)number).doubleValue(), toAppendTo, pos);
} else {
throw new IllegalArgumentException("Cannot format given Object as a Number");
}
}
@Override
public final Object parseObject(String source, ParsePosition pos) {
return parse(source, pos);
}
public final String format(double number) {
// Use fast-path for double result if that works
String result = fastFormat(number);
if (result != null)
return result;
return format(number, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
}
/*
* fastFormat() is supposed to be implemented in concrete subclasses only.
* Default implem always returns null.
*/
String fastFormat(double number) { return null; }
public final String format(long number) {
return format(number, new StringBuffer(),
DontCareFieldPosition.INSTANCE).toString();
}
public abstract StringBuffer format(double number,
StringBuffer toAppendTo,
FieldPosition pos);
public abstract StringBuffer format(long number,
StringBuffer toAppendTo,
FieldPosition pos);
public abstract Number parse(String source, ParsePosition parsePosition);
public Number parse(String source) throws ParseException {
ParsePosition parsePosition = new ParsePosition(0);
Number result = parse(source, parsePosition);
if (parsePosition.index == 0) {
throw new ParseException("Unparseable number: \"" + source + "\"",
parsePosition.errorIndex);
}
return result;
}
public boolean isParseIntegerOnly() {
return parseIntegerOnly;
}
public void setParseIntegerOnly(boolean value) {
parseIntegerOnly = value;
}
//============== Locale Stuff =====================
public static final NumberFormat getInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE);
}
public static NumberFormat getInstance(Locale inLocale) {
return getInstance(inLocale, null, NUMBERSTYLE);
}
public static final NumberFormat getNumberInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE);
}
public static NumberFormat getNumberInstance(Locale inLocale) {
return getInstance(inLocale, null, NUMBERSTYLE);
}
public static final NumberFormat getIntegerInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, INTEGERSTYLE);
}
public static NumberFormat getIntegerInstance(Locale inLocale) {
return getInstance(inLocale, null, INTEGERSTYLE);
}
public static final NumberFormat getCurrencyInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, CURRENCYSTYLE);
}
public static NumberFormat getCurrencyInstance(Locale inLocale) {
return getInstance(inLocale, null, CURRENCYSTYLE);
}
public static final NumberFormat getPercentInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, PERCENTSTYLE);
}
public static NumberFormat getPercentInstance(Locale inLocale) {
return getInstance(inLocale, null, PERCENTSTYLE);
}
/*public*/ final static NumberFormat getScientificInstance() {
return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, SCIENTIFICSTYLE);
}
/*public*/ static NumberFormat getScientificInstance(Locale inLocale) {
return getInstance(inLocale, null, SCIENTIFICSTYLE);
}
public static NumberFormat getCompactNumberInstance() {
return getInstance(Locale.getDefault(
Locale.Category.FORMAT), NumberFormat.Style.SHORT, COMPACTSTYLE);
}
public static NumberFormat getCompactNumberInstance(Locale locale,
NumberFormat.Style formatStyle) {
Objects.requireNonNull(locale);
Objects.requireNonNull(formatStyle);
return getInstance(locale, formatStyle, COMPACTSTYLE);
}
public static Locale[] getAvailableLocales() {
LocaleServiceProviderPool pool =
LocaleServiceProviderPool.getPool(NumberFormatProvider.class);
return pool.getAvailableLocales();
}
@Override
public int hashCode() {
return maximumIntegerDigits * 37 + maxFractionDigits;
// just enough fields for a reasonable distribution
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
NumberFormat other = (NumberFormat) obj;
return (maximumIntegerDigits == other.maximumIntegerDigits
&& minimumIntegerDigits == other.minimumIntegerDigits
&& maximumFractionDigits == other.maximumFractionDigits
&& minimumFractionDigits == other.minimumFractionDigits
&& groupingUsed == other.groupingUsed
&& parseIntegerOnly == other.parseIntegerOnly);
}
@Override
public Object clone() {
NumberFormat other = (NumberFormat) super.clone();
return other;
}
public boolean isGroupingUsed() {
return groupingUsed;
}
public void setGroupingUsed(boolean newValue) {
groupingUsed = newValue;
}
public int getMaximumIntegerDigits() {
return maximumIntegerDigits;
}
public void setMaximumIntegerDigits(int newValue) {
maximumIntegerDigits = Math.max(0,newValue);
if (minimumIntegerDigits > maximumIntegerDigits) {
minimumIntegerDigits = maximumIntegerDigits;
}
}
public int getMinimumIntegerDigits() {
return minimumIntegerDigits;
}
public void setMinimumIntegerDigits(int newValue) {
minimumIntegerDigits = Math.max(0,newValue);
if (minimumIntegerDigits > maximumIntegerDigits) {
maximumIntegerDigits = minimumIntegerDigits;
}
}
public int getMaximumFractionDigits() {
return maximumFractionDigits;
}
public void setMaximumFractionDigits(int newValue) {
maximumFractionDigits = Math.max(0,newValue);
if (maximumFractionDigits < minimumFractionDigits) {
minimumFractionDigits = maximumFractionDigits;
}
}
public int getMinimumFractionDigits() {
return minimumFractionDigits;
}
public void setMinimumFractionDigits(int newValue) {
minimumFractionDigits = Math.max(0,newValue);
if (maximumFractionDigits < minimumFractionDigits) {
maximumFractionDigits = minimumFractionDigits;
}
}
public Currency getCurrency() {
throw new UnsupportedOperationException();
}
public void setCurrency(Currency currency) {
throw new UnsupportedOperationException();
}
public RoundingMode getRoundingMode() {
throw new UnsupportedOperationException();
}
public void setRoundingMode(RoundingMode roundingMode) {
throw new UnsupportedOperationException();
}
// =======================privates===============================
private static NumberFormat getInstance(Locale desiredLocale,
Style formatStyle, int choice) {
LocaleProviderAdapter adapter;
adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class,
desiredLocale);
NumberFormat numberFormat = getInstance(adapter, desiredLocale,
formatStyle, choice);
if (numberFormat == null) {
numberFormat = getInstance(LocaleProviderAdapter.forJRE(),
desiredLocale, formatStyle, choice);
}
return numberFormat;
}
private static NumberFormat getInstance(LocaleProviderAdapter adapter,
Locale locale, Style formatStyle,
int choice) {
NumberFormatProvider provider = adapter.getNumberFormatProvider();
NumberFormat numberFormat = null;
switch (choice) {
case NUMBERSTYLE:
numberFormat = provider.getNumberInstance(locale);
break;
case PERCENTSTYLE:
numberFormat = provider.getPercentInstance(locale);
break;
case CURRENCYSTYLE:
numberFormat = provider.getCurrencyInstance(locale);
break;
case INTEGERSTYLE:
numberFormat = provider.getIntegerInstance(locale);
break;
case COMPACTSTYLE:
numberFormat = provider.getCompactNumberInstance(locale, formatStyle);
break;
}
return numberFormat;
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException
{
stream.defaultReadObject();
if (serialVersionOnStream < 1) {
// Didn't have additional int fields, reassign to use them.
maximumIntegerDigits = maxIntegerDigits;
minimumIntegerDigits = minIntegerDigits;
maximumFractionDigits = maxFractionDigits;
minimumFractionDigits = minFractionDigits;
}
if (minimumIntegerDigits > maximumIntegerDigits ||
minimumFractionDigits > maximumFractionDigits ||
minimumIntegerDigits < 0 || minimumFractionDigits < 0) {
throw new InvalidObjectException("Digit count range invalid");
}
serialVersionOnStream = currentSerialVersion;
}
private void writeObject(ObjectOutputStream stream)
throws IOException
{
maxIntegerDigits = (maximumIntegerDigits > Byte.MAX_VALUE) ?
Byte.MAX_VALUE : (byte)maximumIntegerDigits;
minIntegerDigits = (minimumIntegerDigits > Byte.MAX_VALUE) ?
Byte.MAX_VALUE : (byte)minimumIntegerDigits;
maxFractionDigits = (maximumFractionDigits > Byte.MAX_VALUE) ?
Byte.MAX_VALUE : (byte)maximumFractionDigits;
minFractionDigits = (minimumFractionDigits > Byte.MAX_VALUE) ?
Byte.MAX_VALUE : (byte)minimumFractionDigits;
stream.defaultWriteObject();
}
// Constants used by factory methods to specify a style of format.
private static final int NUMBERSTYLE = 0;
private static final int CURRENCYSTYLE = 1;
private static final int PERCENTSTYLE = 2;
private static final int SCIENTIFICSTYLE = 3;
private static final int INTEGERSTYLE = 4;
private static final int COMPACTSTYLE = 5;
private boolean groupingUsed = true;
private byte maxIntegerDigits = 40;
private byte minIntegerDigits = 1;
private byte maxFractionDigits = 3; // invariant, >= minFractionDigits
private byte minFractionDigits = 0;
private boolean parseIntegerOnly = false;
// new fields for 1.2. byte is too small for integer digits.
private int maximumIntegerDigits = 40;
private int minimumIntegerDigits = 1;
private int maximumFractionDigits = 3; // invariant, >= minFractionDigits
private int minimumFractionDigits = 0;
static final int currentSerialVersion = 1;
private int serialVersionOnStream = currentSerialVersion;
// Removed "implements Cloneable" clause. Needs to update serialization
// ID for backward compatibility.
static final long serialVersionUID = -2308460125733713944L;
//
// class for AttributedCharacterIterator attributes
//
public static class Field extends Format.Field {
// Proclaim serial compatibility with 1.4 FCS
private static final long serialVersionUID = 7494728892700160890L;
// table of all instances in this class, used by readResolve
private static final Map<String, Field> instanceMap = new HashMap<>(11);
protected Field(String name) {
super(name);
if (this.getClass() == NumberFormat.Field.class) {
instanceMap.put(name, this);
}
}
@Override
protected Object readResolve() throws InvalidObjectException {
if (this.getClass() != NumberFormat.Field.class) {
throw new InvalidObjectException("subclass didn't correctly implement readResolve");
}
Object instance = instanceMap.get(getName());
if (instance != null) {
return instance;
} else {
throw new InvalidObjectException("unknown attribute name");
}
}
public static final Field INTEGER = new Field("integer");
public static final Field FRACTION = new Field("fraction");
public static final Field EXPONENT = new Field("exponent");
public static final Field DECIMAL_SEPARATOR =
new Field("decimal separator");
public static final Field SIGN = new Field("sign");
public static final Field GROUPING_SEPARATOR =
new Field("grouping separator");
public static final Field EXPONENT_SYMBOL = new
Field("exponent symbol");
public static final Field PERCENT = new Field("percent");
public static final Field PERMILLE = new Field("per mille");
public static final Field CURRENCY = new Field("currency");
public static final Field EXPONENT_SIGN = new Field("exponent sign");
public static final Field PREFIX = new Field("prefix");
public static final Field SUFFIX = new Field("suffix");
}
public enum Style {
SHORT,
LONG
}
}
java.text.NumberFormatクラスは数値を書式化するための抽象クラスである。ソースコードは,1300行を超える比較的大きなクラスである。
コンストラクタはprotectedであり,サブクラスからしか呼び出せないが,中身は空である。
format(Object number, StringBuffer toAppendTo, FieldPosition pos)メソッドは,このクラスの主要なメソッドであり,数値を書式化する。数値が整数型,つまり,Long,Integer,Short,Byte,AtomicInteger,AtomicLong,BigInteger,および,64ビット未満のBigIntegerの場合は, 数値をNumber型にキャストした上で,抽象メソッド format(long number, StringBuffer toAppendTo, FieldPosition pos)へ委譲している。数値がNumber型の場合は,浮動小数点型のdoubleにキャストして抽象メソッドformat(double number, StringBuffer toAppendTo, FieldPosition pos)へ委譲している。これら以外の型の場合は,IllegalArgumentExceptionをスローしている。
parseObject(String source, ParsePosition pos)メソッドは,文字列を解析して数値を取得するものである。抽象メソッドparse( String source, ParsePosition parsePosition )へ委譲している。
format(double number)メソッドは,double型を文字列にする。まずfastFormat(double number)メソッドでフォーマットを試みるが,このクラスの fastFormat(double number) メソッドは常にnullを返す実装となっているため,この部分はサブクラスでオーバーライドしない限りは意味がない。従って,format(number, new StringBuffer(), DontCareFieldPosition. INSTANCE ) .toString()が実行されて返される。
format(long number)メソッドも同様にformat(number, new StringBuffer(), DontCareFieldPosition .INSTANCE ).toString()を呼び出すのみである。
parse(String source)メソッドは,文字列を解析して数値に変換する。解析開始位置 parsePosition はディフォルト値0を用いてparse(source, parsePosition)を実行する。
中段からは,ロケールに対応したインスタンスを取得するためのメソッドが定義されている。
- getInstance()
- getInstance(Locale inLocale)
- getNumberInstance()
- getNumberInstance(Locale inLocale)
- getIntegerInstance()
- getIntegerInstance(Locale inLocale)
- getCurrencyInstance()
- getCurrencyInstance(Locale inLocale)
- getPercentInstance()
- getPercentInstance(Locale inLocale)
- getScientificInstance()
- getScientificInstance(Locale inLocale)
- getCompactNumberInstance()
- getCompactNumberInstance(Locale locale, NumberFormat.Style formatStyle)
getAvailableLocales()メソッドでは,使用可能なロケールオブジェクトの配列を返す。
getCurrency()メソッドやsetCurrency(Currency currency)メソッド,getRoundingMode()メソッド,setRoundingMode(RoundingMode roundingMode)メソッドは実装されておらず,実行するとUnsupportedOperationExceptionがスローされるようになっている。
クラスの後段に各種のインスタンス変数定義が置かれている。最後の部分には,インナークラスFieldとStyleというenumが定義されている。