← joda-money  /  src/main/java/org/joda/money/format/AmountPrinterParser.java

1
/*
2
 *  Copyright 2009-present, Stephen Colebourne
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 */
16
package org.joda.money.format;
17
18
import java.io.IOException;
19
import java.io.Serializable;
20
import java.math.BigDecimal;
21
22
import org.joda.money.BigMoney;
23
24
/**
25
 * Prints and parses the amount part of the money.
26
 * <p>
27
 * This class is immutable and thread-safe.
28
 */
29
final class AmountPrinterParser implements MoneyPrinter, MoneyParser, Serializable {
30
31
    /** Serialization version. */
32
    private static final long serialVersionUID = 1L;
33
34
    /** The style to use. */
35
    private final MoneyAmountStyle style;
36
37
    /**
38
     * Constructor.
39
     * @param style  the style, not null
40
     */
41
    AmountPrinterParser(MoneyAmountStyle style) {
42
        this.style = style;
43
    }
44
45
    //-----------------------------------------------------------------------
46
    @Override
47
    public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
48
        var activeStyle = style.localize(context.getLocale());
49
        String str;
50
        if (money.isNegative()) {
51
            if (!activeStyle.isAbsValue()) {
52
                appendable.append(activeStyle.getNegativeSignCharacter());
53
            }
54
            str = money.negated().getAmount().toPlainString();
55
        } else {
56
            str = money.getAmount().toPlainString();
57
        }
58
        var zeroChar = activeStyle.getZeroCharacter();
59
        if (zeroChar != '0') {
60
            var diff = zeroChar - '0';
61
            var zeroConvert = new StringBuilder(str);
62
            for (var i = 0; i < str.length(); i++) {
63
                var ch = str.charAt(i);
64
                if (ch >= '0' && ch <= '9') {
65
                    zeroConvert.setCharAt(i, (char) (ch + diff));
66
                }
67
            }
68
            str = zeroConvert.toString();
69
        }
70
        var decPoint = str.indexOf('.');
71
        var afterDecPoint = decPoint + 1;
72
        if (activeStyle.getGroupingStyle() == GroupingStyle.NONE) {
73
            if (decPoint < 0) {
74
                appendable.append(str);
75
                if (activeStyle.isForcedDecimalPoint()) {
76
                    appendable.append(activeStyle.getDecimalPointCharacter());
77
                }
78
            } else {
79
                appendable.append(str.subSequence(0, decPoint))
80
                    .append(activeStyle.getDecimalPointCharacter()).append(str.substring(afterDecPoint));
81
            }
82
        } else {
83
            var groupingSize = activeStyle.getGroupingSize();
84
            var extendedGroupingSize = activeStyle.getExtendedGroupingSize();
85
            extendedGroupingSize = extendedGroupingSize == 0 ? groupingSize : extendedGroupingSize;
86
            var groupingChar = activeStyle.getGroupingCharacter();
87
            var pre = (decPoint < 0 ? str.length() : decPoint);
88
            var post = (decPoint < 0 ? 0 : str.length() - decPoint - 1);
89
            appendable.append(str.charAt(0));
90
            for (var i = 1; i < pre; i++) {
91
                if (isPreGroupingPoint(pre - i, groupingSize, extendedGroupingSize)) {
92
                    appendable.append(groupingChar);
93
                }
94
                appendable.append(str.charAt(i));
95
            }
96
            if (decPoint >= 0 || activeStyle.isForcedDecimalPoint()) {
97
                appendable.append(activeStyle.getDecimalPointCharacter());
98
            }
99
            if (activeStyle.getGroupingStyle() == GroupingStyle.BEFORE_DECIMAL_POINT) {
100
                if (decPoint >= 0) {
101
                    appendable.append(str.substring(afterDecPoint));
102
                }
103
            } else {
104
                for (var i = 0; i < post; i++) {
105
                    appendable.append(str.charAt(i + afterDecPoint));
106
                    if (isPostGroupingPoint(i, post, groupingSize, extendedGroupingSize)) {
107
                        appendable.append(groupingChar);
108
                    }
109
                }
110
            }
111
        }
112
    }
113
114
    private boolean isPreGroupingPoint(int remaining, int groupingSize, int extendedGroupingSize) {
115
        if (remaining >= groupingSize + extendedGroupingSize) {
116
            return (remaining - groupingSize) % extendedGroupingSize == 0;
117
        }
118
        return remaining % groupingSize == 0;
119
    }
120
121
    private boolean isPostGroupingPoint(int i, int post, int groupingSize, int extendedGroupingSize) {
122
        var atEnd = (i + 1) >= post;
123
        if (i > groupingSize) {
124
            return (i - groupingSize) % extendedGroupingSize == (extendedGroupingSize - 1) && !atEnd;
125
        }
126
        return i % groupingSize == (groupingSize - 1) && !atEnd;
127
    }
128
129
    @Override
130
    public void parse(MoneyParseContext context) {
131
        var len = context.getTextLength();
132
        var activeStyle = style.localize(context.getLocale());
133
        var buf = new char[len - context.getIndex()];
134
        var bufPos = 0;
135
        var dpSeen = false;
136
        var pos = context.getIndex();
137
        if (pos < len) {
138
            var ch = context.getText().charAt(pos++);
139
            if (ch == activeStyle.getNegativeSignCharacter()) {
140
                buf[bufPos++] = '-';
141
            } else if (ch == activeStyle.getPositiveSignCharacter()) {
142
                buf[bufPos++] = '+';
143
            } else if (ch >= activeStyle.getZeroCharacter() && ch < activeStyle.getZeroCharacter() + 10) {
144
                buf[bufPos++] = (char) ('0' + ch - activeStyle.getZeroCharacter());
145
            } else if (ch == activeStyle.getDecimalPointCharacter()) {
146
                buf[bufPos++] = '.';
147
                dpSeen = true;
148
            } else {
149
                context.setError();
150
                return;
151
            }
152
        }
153
        var lastWasGroup = false;
154
        for (; pos < len; pos++) {
155
            var ch = context.getText().charAt(pos);
156
            if (ch >= activeStyle.getZeroCharacter() && ch < activeStyle.getZeroCharacter() + 10) {
157
                buf[bufPos++] = (char) ('0' + ch - activeStyle.getZeroCharacter());
158
                lastWasGroup = false;
159
            } else if (ch == activeStyle.getDecimalPointCharacter() && !dpSeen) {
160
                buf[bufPos++] = '.';
161
                dpSeen = true;
162
                lastWasGroup = false;
163
            } else if (ch == activeStyle.getGroupingCharacter() && !lastWasGroup) {
164
                lastWasGroup = true;
165
            } else {
166
                break;
167
            }
168
        }
169
        if (lastWasGroup) {
170
            pos--;
171
        }
172
        try {
173
            context.setAmount(new BigDecimal(buf, 0, bufPos));
174
            context.setIndex(pos);
175
        } catch (NumberFormatException ex) {
176
            context.setError();
177
        }
178
    }
179
180
    @Override
181
    public String toString() {
182
        return "${amount}";
183
    }
184
185
}
186