1// © 2017 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3package com.ibm.icu.number;
4
5import java.io.IOException;
6import java.math.BigDecimal;
7import java.text.AttributedCharacterIterator;
8import java.text.FieldPosition;
9import java.util.Arrays;
10
11import com.ibm.icu.impl.number.DecimalQuantity;
12import com.ibm.icu.impl.number.MicroProps;
13import com.ibm.icu.impl.number.NumberStringBuilder;
14import com.ibm.icu.text.PluralRules.IFixedDecimal;
15import com.ibm.icu.util.ICUUncheckedIOException;
16
17/**
18 * The result of a number formatting operation. This class allows the result to be exported in several data types,
19 * including a String, an AttributedCharacterIterator, and a BigDecimal.
20 *
21 * @draft ICU 60
22 * @provisional This API might change or be removed in a future release.
23 * @see NumberFormatter
24 */
25public class FormattedNumber {
26    NumberStringBuilder nsb;
27    DecimalQuantity fq;
28    MicroProps micros;
29
30    FormattedNumber(NumberStringBuilder nsb, DecimalQuantity fq, MicroProps micros) {
31        this.nsb = nsb;
32        this.fq = fq;
33        this.micros = micros;
34    }
35
36    /**
37     * Creates a String representation of the the formatted number.
38     *
39     * @return a String containing the localized number.
40     * @draft ICU 60
41     * @provisional This API might change or be removed in a future release.
42     * @see NumberFormatter
43     */
44    @Override
45    public String toString() {
46        return nsb.toString();
47    }
48
49    /**
50     * Append the formatted number to an Appendable, such as a StringBuilder. This may be slightly more efficient than
51     * creating a String.
52     *
53     * <p>
54     * If an IOException occurs when appending to the Appendable, an unchecked {@link ICUUncheckedIOException} is thrown
55     * instead.
56     *
57     * @param appendable
58     *            The Appendable to which to append the formatted number string.
59     * @return The same Appendable, for chaining.
60     * @draft ICU 60
61     * @provisional This API might change or be removed in a future release.
62     * @see Appendable
63     * @see NumberFormatter
64     */
65    public <A extends Appendable> A appendTo(A appendable) {
66        try {
67            appendable.append(nsb);
68        } catch (IOException e) {
69            // Throw as an unchecked exception to avoid users needing try/catch
70            throw new ICUUncheckedIOException(e);
71        }
72        return appendable;
73    }
74
75    /**
76     * Determine the start and end indices of the first occurrence of the given <em>field</em> in the output string.
77     * This allows you to determine the locations of the integer part, fraction part, and sign.
78     *
79     * <p>
80     * If multiple different field attributes are needed, this method can be called repeatedly, or if <em>all</em> field
81     * attributes are needed, consider using getFieldIterator().
82     *
83     * <p>
84     * If a field occurs multiple times in an output string, such as a grouping separator, this method will only ever
85     * return the first occurrence. Use getFieldIterator() to access all occurrences of an attribute.
86     *
87     * @param fieldPosition
88     *            The FieldPosition to populate with the start and end indices of the desired field.
89     * @draft ICU 60
90     * @provisional This API might change or be removed in a future release.
91     * @see com.ibm.icu.text.NumberFormat.Field
92     * @see NumberFormatter
93     */
94    public void populateFieldPosition(FieldPosition fieldPosition) {
95        populateFieldPosition(fieldPosition, 0);
96    }
97
98    /**
99     * @internal
100     * @deprecated This API is ICU internal only.
101     */
102    @Deprecated
103    public void populateFieldPosition(FieldPosition fieldPosition, int offset) {
104        nsb.populateFieldPosition(fieldPosition, offset);
105        fq.populateUFieldPosition(fieldPosition);
106    }
107
108    /**
109     * Export the formatted number as an AttributedCharacterIterator. This allows you to determine which characters in
110     * the output string correspond to which <em>fields</em>, such as the integer part, fraction part, and sign.
111     *
112     * <p>
113     * If information on only one field is needed, consider using populateFieldPosition() instead.
114     *
115     * @return An AttributedCharacterIterator, containing information on the field attributes of the number string.
116     * @draft ICU 60
117     * @provisional This API might change or be removed in a future release.
118     * @see com.ibm.icu.text.NumberFormat.Field
119     * @see AttributedCharacterIterator
120     * @see NumberFormatter
121     */
122    public AttributedCharacterIterator getFieldIterator() {
123        return nsb.getIterator();
124    }
125
126    /**
127     * Export the formatted number as a BigDecimal. This endpoint is useful for obtaining the exact number being printed
128     * after scaling and rounding have been applied by the number formatting pipeline.
129     *
130     * @return A BigDecimal representation of the formatted number.
131     * @draft ICU 60
132     * @provisional This API might change or be removed in a future release.
133     * @see NumberFormatter
134     */
135    public BigDecimal toBigDecimal() {
136        return fq.toBigDecimal();
137    }
138
139    /**
140     * @internal
141     * @deprecated This API is ICU internal only.
142     */
143    @Deprecated
144    public String getPrefix() {
145        NumberStringBuilder temp = new NumberStringBuilder();
146        int length = micros.modOuter.apply(temp, 0, 0);
147        length += micros.modMiddle.apply(temp, 0, length);
148        /* length += */ micros.modInner.apply(temp, 0, length);
149        int prefixLength = micros.modOuter.getPrefixLength() + micros.modMiddle.getPrefixLength()
150                + micros.modInner.getPrefixLength();
151        return temp.subSequence(0, prefixLength).toString();
152    }
153
154    /**
155     * @internal
156     * @deprecated This API is ICU internal only.
157     */
158    @Deprecated
159    public String getSuffix() {
160        NumberStringBuilder temp = new NumberStringBuilder();
161        int length = micros.modOuter.apply(temp, 0, 0);
162        length += micros.modMiddle.apply(temp, 0, length);
163        length += micros.modInner.apply(temp, 0, length);
164        int prefixLength = micros.modOuter.getPrefixLength() + micros.modMiddle.getPrefixLength()
165                + micros.modInner.getPrefixLength();
166        return temp.subSequence(prefixLength, length).toString();
167    }
168
169    /**
170     * @internal
171     * @deprecated This API is ICU internal only.
172     */
173    @Deprecated
174    public IFixedDecimal getFixedDecimal() {
175        return fq;
176    }
177
178    /**
179     * {@inheritDoc}
180     *
181     * @draft ICU 60
182     * @provisional This API might change or be removed in a future release.
183     */
184    @Override
185    public int hashCode() {
186        // NumberStringBuilder and BigDecimal are mutable, so we can't call
187        // #equals() or #hashCode() on them directly.
188        return Arrays.hashCode(nsb.toCharArray()) ^ Arrays.hashCode(nsb.toFieldArray()) ^ fq.toBigDecimal().hashCode();
189    }
190
191    /**
192     * {@inheritDoc}
193     *
194     * @draft ICU 60
195     * @provisional This API might change or be removed in a future release.
196     */
197    @Override
198    public boolean equals(Object other) {
199        if (this == other)
200            return true;
201        if (other == null)
202            return false;
203        if (!(other instanceof FormattedNumber))
204            return false;
205        // NumberStringBuilder and BigDecimal are mutable, so we can't call
206        // #equals() or #hashCode() on them directly.
207        FormattedNumber _other = (FormattedNumber) other;
208        return Arrays.equals(nsb.toCharArray(), _other.nsb.toCharArray())
209                ^ Arrays.equals(nsb.toFieldArray(), _other.nsb.toFieldArray())
210                ^ fq.toBigDecimal().equals(_other.fq.toBigDecimal());
211    }
212}