4 * License : The MIT License
5 * Copyright(c) 2009 olyutorskii
8 package jp.sourceforge.jovsonz;
10 import java.io.IOException;
11 import java.math.BigDecimal;
12 import java.math.BigInteger;
13 import java.math.MathContext;
14 import java.math.RoundingMode;
17 * JSON NUMBER型Valueを表す。
20 * 10を基数とした{@link java.math.BigDecimal}を実装ベースとする。
23 * <p>(1)と(1.0)はスケール値によって区別される</p>
30 * @see java.math.BigDecimal
33 implements JsValue, Comparable<JsNumber> {
35 private static final MathContext DEF_MC =
36 new MathContext(0, RoundingMode.UNNECESSARY);
38 private static final String ERRMSG_INVFRAC =
39 "invalid fractional number";
40 private static final String ERRMSG_NONUMBER =
42 private static final String ERRMSG_EXTRAZERO =
45 private final BigDecimal decimal;
51 public JsNumber(long val){
52 this(BigDecimal.valueOf(val));
59 * {@link java.math.BigDecimal#valueOf(double)}と同等の丸めが行われる。
60 * (1.0/10.0)を渡すと0.1相当になる。
61 * 必要に応じて{@link java.math.BigDecimal}を
62 * 引数に持つコンストラクタと使い分けること。
65 * @see java.math.BigDecimal#valueOf(double)
67 public JsNumber(double val){
68 this(BigDecimal.valueOf(val));
75 * @throws ArithmeticException 正確な結果を
76 * {@link java.math.BigDecimal}に納め切れない
78 public JsNumber(BigInteger val) throws ArithmeticException{
79 this(new BigDecimal(val, DEF_MC));
85 * 書式は{@link java.math.BigDecimal#BigDecimal(String)}に準ずる。
86 * @param val 初期数値の文字列表記
87 * @throws NumberFormatException 不正な数値表記
88 * @throws ArithmeticException 正確な結果を
89 * {@link java.math.BigDecimal}に納め切れない
90 * @see java.math.BigDecimal#BigDecimal(String)
92 public JsNumber(CharSequence val)
93 throws NumberFormatException, ArithmeticException{
94 this(new BigDecimal(val.toString(), DEF_MC));
101 * @throws NullPointerException 引数がnull
103 public JsNumber(BigDecimal val) throws NullPointerException{
105 if(val == null) throw new NullPointerException();
111 * 任意の文字がUnicodeのBasic-Latinの数字か否か判定する。
114 * @see java.lang.Character#isDigit(char)
116 public static boolean isLatinDigit(char ch){
117 if('0' <= ch && ch <= '9') return true;
122 * 文字ソースから符号付きの数字並びを読み込む。
124 * 冒頭のゼロ'0'に続く数字を許すか否か指定が可能。
125 * <p>NUMBER型表記の整数部、小数部、指数部読み込みの下請けメソッド。</p>
126 * @param source 文字列ソース
128 * @param allowZeroTrail 冒頭のゼロ'0'に続く数字を許すならtrue
130 * @throws IOException 入出力エラー
131 * @throws JsParseException 不正な書式もしくは意図しない入力終了
133 private static Appendable appendDigitText(JsonSource source,
135 boolean allowZeroTrail)
136 throws IOException, JsParseException{
137 char head = source.readOrDie();
138 if (head == '-') app.append('-');
139 else if(head != '+') source.unread(head);
141 boolean hasAppended = false;
142 boolean zeroStarted = false; // 先頭は0か
144 if( ! source.hasMore() && hasAppended) break;
146 char readedCh = source.readOrDie();
148 if( ! isLatinDigit(readedCh) ){
150 throw new JsParseException(ERRMSG_NONUMBER,
151 source.getLineNumber() );
153 source.unread(readedCh);
158 if(zeroStarted && ! allowZeroTrail){
159 throw new JsParseException(ERRMSG_EXTRAZERO,
160 source.getLineNumber() );
168 app.append(readedCh);
176 * 文字ソースから、ピリオド「.」で始まるNUMBER型小数部を読み込む。
178 * @param source 文字列ソース
181 * @throws IOException 入出力エラー
182 * @throws JsParseException 不正な書式もしくは意図しない入力終了
184 private static Appendable appendFractionPart(JsonSource source,
186 throws IOException, JsParseException{
187 if( ! source.hasMore() ) return app;
191 chData = source.readOrDie();
193 source.unread(chData);
199 boolean hasAppended = false;
201 if( ! source.hasMore() && hasAppended) break;
203 chData = source.readOrDie();
205 if( ! isLatinDigit(chData) ){
207 throw new JsParseException(ERRMSG_INVFRAC,
208 source.getLineNumber());
210 source.unread(chData);
222 * 文字ソースから「e」もしくは「E」で始まるNUMBER型指数部を読み込む。
224 * @param source 文字列ソース
227 * @throws IOException 入出力エラー
228 * @throws JsParseException 不正な書式もしくは意図しない入力終了
230 private static Appendable appendExpPart(JsonSource source,
232 throws IOException, JsParseException{
233 if( ! source.hasMore() ) return app;
235 char chData = source.readOrDie();
236 if(chData != 'e' && chData != 'E'){
237 source.unread(chData);
243 appendDigitText(source, app, true);
249 * JSON文字列ソースからNUMBER型Valueを読み込む。
250 * 別型の可能性のある先頭文字を読み込んだ場合、
251 * ソースに文字を読み戻した後nullが返される。
252 * @param source 文字列ソース
253 * @return NUMBER型Value。別型の可能性がある場合はnull。
254 * @throws IOException 入力エラー
255 * @throws JsParseException 不正な表記もしくは意図しない入力終了
257 static JsNumber parseNumber(JsonSource source)
258 throws IOException, JsParseException{
259 char charHead = source.readOrDie();
260 source.unread(charHead);
261 if( charHead != '-' && ! JsNumber.isLatinDigit(charHead) ){
265 StringBuilder numText = new StringBuilder();
267 appendDigitText (source, numText, false);
268 appendFractionPart(source, numText);
269 appendExpPart (source, numText);
271 JsNumber result = new JsNumber(numText);
278 * 常に{@link JsTypes#NUMBER}を返す。
279 * @return {@inheritDoc}
282 public JsTypes getJsTypes(){
283 return JsTypes.NUMBER;
288 * この実装ではthisの出現のみを通知する。
289 * @param visitor {@inheritDoc}
290 * @throws JsVisitException {@inheritDoc}
293 public void traverse(ValueVisitor visitor)
294 throws JsVisitException{
295 visitor.visitValue(this);
302 * {@link java.math.BigDecimal#hashCode()}と同じ値を返す。
303 * @return {@inheritDoc}
304 * @see java.math.BigDecimal#hashCode()
307 public int hashCode(){
308 return this.decimal.hashCode();
314 * {@link java.math.BigDecimal#equals(Object)}と同等の判断が行われる。
316 * スケールの一致しない値は異なる値と見なされる。
317 * @param obj {@inheritDoc}
318 * @return {@inheritDoc}
319 * @see java.math.BigDecimal#equals(Object)
322 public boolean equals(Object obj){
323 if(this == obj) return true;
324 if( ! (obj instanceof JsNumber) ) return false;
325 JsNumber number = (JsNumber) obj;
326 return this.decimal.equals(number.decimal);
331 * NUMBER型Valueを昇順に順序付ける。
332 * 「1.2」と「0.12E+1」など、スケールが異なっても値が同じであれば
334 * @param value {@inheritDoc}
335 * @return {@inheritDoc}
336 * @see java.math.BigDecimal#compareTo(BigDecimal)
339 public int compareTo(JsNumber value){
340 if(this == value) return 0;
341 return this.decimal.compareTo(value.decimal);
348 * @see java.lang.Number#intValue()
349 * @see java.math.BigDecimal#intValue()
351 public int intValue(){
352 return this.decimal.intValue();
359 * @see java.lang.Number#longValue()
360 * @see java.math.BigDecimal#longValue()
362 public long longValue(){
363 return this.decimal.longValue();
370 * @see java.lang.Number#floatValue()
371 * @see java.math.BigDecimal#floatValue()
373 public float floatValue(){
374 return this.decimal.floatValue();
381 * @see java.lang.Number#doubleValue()
382 * @see java.math.BigDecimal#doubleValue()
384 public double doubleValue(){
385 return this.decimal.doubleValue();
389 * {@link java.math.BigDecimal}型の数値表現を返す。
390 * @return BigDecimal型数値
392 public BigDecimal decimalValue(){
398 * このインスタンスが整数文字列表記に由来する場合、
404 * <li>"99.01"のスケール値は2
405 * <li>"99E+3"のスケール値は-3
406 * <li>"99.0E+3"のスケール値は-2
410 * @see java.math.BigDecimal#scale()
413 return this.decimal.scale();
418 * {@link java.math.BigDecimal#toString()}に準ずる。
419 * JSON表記の一部としての利用も可能。
420 * @return {@inheritDoc}
423 public String toString(){
424 return this.decimal.toString();