├── .github
├── FUNDING.yml
├── SECURITY.md
├── maven-settings.xml
└── workflows
│ ├── build.yml
│ ├── release.yml
│ └── website.yml
├── .gitignore
├── LICENSE.txt
├── NOTICE.txt
├── Notes.txt
├── README.md
├── RELEASE-NOTES.txt
├── pom.xml
└── src
├── changes
└── changes.xml
├── eclipse
└── JodaMoneyFormatter.xml
├── main
├── assembly
│ └── dist.xml
├── checkstyle
│ └── checkstyle.xml
├── java
│ ├── module-info.java
│ └── org
│ │ └── joda
│ │ └── money
│ │ ├── BigMoney.java
│ │ ├── BigMoneyProvider.java
│ │ ├── CurrencyMismatchException.java
│ │ ├── CurrencyUnit.java
│ │ ├── CurrencyUnitDataProvider.java
│ │ ├── DefaultCurrencyUnitDataProvider.java
│ │ ├── IllegalCurrencyException.java
│ │ ├── Money.java
│ │ ├── MoneyUtils.java
│ │ ├── Ser.java
│ │ └── format
│ │ ├── AmountPrinterParser.java
│ │ ├── GroupingStyle.java
│ │ ├── LiteralPrinterParser.java
│ │ ├── MoneyAmountStyle.java
│ │ ├── MoneyFormatException.java
│ │ ├── MoneyFormatter.java
│ │ ├── MoneyFormatterBuilder.java
│ │ ├── MoneyParseContext.java
│ │ ├── MoneyParser.java
│ │ ├── MoneyPrintContext.java
│ │ ├── MoneyPrinter.java
│ │ ├── MultiPrinterParser.java
│ │ └── SignedPrinterParser.java
└── resources
│ ├── META-INF
│ └── proguard
│ │ └── jodamoney.pro
│ └── org
│ └── joda
│ └── money
│ ├── CountryData.csv
│ └── CurrencyData.csv
├── site
├── markdown
│ ├── enterprise.md
│ └── index.md
├── resources
│ ├── css
│ │ └── site.css
│ └── download.html
├── site.xml
└── xdoc
│ └── userguide.xml
├── test-whitebox
└── module-info.java
└── test
├── java
└── org
│ └── joda
│ └── money
│ ├── TestBigMoney.java
│ ├── TestCurrencyMismatchException.java
│ ├── TestCurrencyUnit.java
│ ├── TestCurrencyUnitExtension.java
│ ├── TestIllegalCurrencyException.java
│ ├── TestModulepath.java
│ ├── TestMoney.java
│ ├── TestMoneyUtils_BigMoney.java
│ ├── TestMoneyUtils_Money.java
│ ├── TestStringConvert.java
│ └── format
│ ├── TestMoneyAmountStyle.java
│ ├── TestMoneyFormatter.java
│ ├── TestMoneyFormatterBuilder.java
│ ├── TestMoneyFormatterException.java
│ └── TestMoneyParseContext.java
└── resources
└── META-INF
└── org
└── joda
└── money
├── CountryDataExtension.csv
└── CurrencyDataExtension.csv
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: jodastephen
2 | open_collective: joda
3 | tidelift: maven/org.joda:joda-money
4 |
5 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository
6 |
--------------------------------------------------------------------------------
/.github/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | If a security issue occurs, only the latest versions of v2.x and v1.x are guaranteed to be patched.
6 |
7 | ## Reporting a Vulnerability
8 |
9 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
10 | Tidelift will coordinate the fix and disclosure.
11 |
--------------------------------------------------------------------------------
/.github/maven-settings.xml:
--------------------------------------------------------------------------------
1 |
20 | * Joda-Money does not provide monetary algorithms beyond the most basic and obvious. 21 | * This is because the requirements for these algorithms vary widely between domains. 22 | * This library is intended to act as the base layer, providing classes that should be in the JDK. 23 | *
24 | * As a flavour of Joda-Money, here's some example code: 25 | *
26 | * // create a monetary value 27 | * Money money = Money.parse("USD 23.87"); 28 | * 29 | * // add another amount with safe double conversion 30 | * CurrencyUnit usd = CurrencyUnit.of("USD"); 31 | * money = money.plus(Money.of(usd, 12.43d)); 32 | *33 | */ 34 | module org.joda.money { 35 | 36 | // only annotations are used, thus they are optional 37 | requires static org.joda.convert; 38 | 39 | // all packages are exported 40 | exports org.joda.money; 41 | exports org.joda.money.format; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/joda/money/BigMoneyProvider.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; 17 | 18 | /** 19 | * Provides a uniform interface to obtain a {@code BigMoney}. 20 | *
21 | * This interface provides an abstraction over {@link Money} and {@link BigMoney}. 22 | * In general, applications should use the concrete types, not this interface. 23 | * However, utilities and frameworks may choose to make use of this abstraction. 24 | *
25 | * Implementations of {@code BigMoneyProvider} may be mutable. 26 | * To minimise the risk of the value of the provider changing while processing, 27 | * any method that takes a {@code BigMoneyProvider} as a parameter should convert 28 | * it to a {@code BigMoney} immediately and use that directly from then on. 29 | * The method {@link BigMoney#of(BigMoneyProvider)} performs the conversion 30 | * safely with null checks and is recommended for this purpose. 31 | *
32 | * This interface makes no guarantees about the immutability or 33 | * thread-safety of implementations. 34 | */ 35 | public interface BigMoneyProvider { 36 | 37 | /** 38 | * Returns a {@code BigMoney} instance equivalent to the value of this object. 39 | *
40 | * It is recommended that {@link BigMoney#of(BigMoneyProvider)} is used in 41 | * preference to calling this method directly. It is also recommended that the 42 | * converted {@code BigMoney} is cached in a local variable instead of 43 | * performing the conversion multiple times. 44 | * 45 | * @return the converted money instance, never null 46 | * @throws RuntimeException if conversion is not possible 47 | */ 48 | public abstract BigMoney toBigMoney(); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/joda/money/CurrencyMismatchException.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; 17 | 18 | /** 19 | * Exception thrown when a monetary operation fails due to mismatched currencies. 20 | *
21 | * For example, this exception would be thrown when trying to add a monetary 22 | * value in one currency to a monetary value in a different currency. 23 | *
24 | * This exception makes no guarantees about immutability or thread-safety. 25 | */ 26 | public class CurrencyMismatchException extends IllegalArgumentException { 27 | 28 | /** Serialization lock. */ 29 | private static final long serialVersionUID = 1L; 30 | 31 | /** First currency. */ 32 | private final CurrencyUnit firstCurrency; 33 | /** Second currency. */ 34 | private final CurrencyUnit secondCurrency; 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param firstCurrency the first currency, may be null 40 | * @param secondCurrency the second currency, not null 41 | */ 42 | public CurrencyMismatchException(CurrencyUnit firstCurrency, CurrencyUnit secondCurrency) { 43 | super("Currencies differ: " + 44 | (firstCurrency != null ? firstCurrency.getCode() : "null") + '/' + 45 | (secondCurrency != null ? secondCurrency.getCode() : "null")); 46 | this.firstCurrency = firstCurrency; 47 | this.secondCurrency = secondCurrency; 48 | } 49 | 50 | //----------------------------------------------------------------------- 51 | /** 52 | * Gets the first currency at fault. 53 | * 54 | * @return the currency at fault, may be null 55 | */ 56 | public CurrencyUnit getFirstCurrency() { 57 | return firstCurrency; 58 | } 59 | 60 | /** 61 | * Gets the second currency at fault. 62 | * 63 | * @return the currency at fault, may be null 64 | */ 65 | public CurrencyUnit getSecondCurrency() { 66 | return secondCurrency; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/joda/money/CurrencyUnitDataProvider.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; 17 | 18 | /** 19 | * Provider for available currencies. 20 | */ 21 | public abstract class CurrencyUnitDataProvider { 22 | 23 | /** 24 | * Registers all the currencies known by this provider. 25 | * 26 | * @throws Exception if an error occurs 27 | */ 28 | protected abstract void registerCurrencies() throws Exception; 29 | 30 | /** 31 | * Registers a currency allowing it to be used. 32 | *
33 | * This method is called by {@link #registerCurrencies()} to perform the 34 | * actual creation of a currency. 35 | * 36 | * @param currencyCode the currency code, not null 37 | * @param numericCurrencyCode the numeric currency code, -1 if none 38 | * @param decimalPlaces the number of decimal places that the currency 39 | * normally has, from 0 to 3, or -1 for a pseudo-currency 40 | */ 41 | protected final void registerCurrency(String currencyCode, int numericCurrencyCode, int decimalPlaces) { 42 | CurrencyUnit.registerCurrency(currencyCode, numericCurrencyCode, decimalPlaces, true); 43 | } 44 | 45 | /** 46 | * Registers a country allowing it to be used. 47 | *
48 | * This method is called by {@link #registerCurrencies()} to perform the 49 | * actual creation of a country. 50 | * 51 | * @param countryCode the country code, not null 52 | * @param currencyCode the currency code, not null 53 | */ 54 | protected final void registerCountry(String countryCode, String currencyCode) { 55 | CurrencyUnit.registerCountry(countryCode, CurrencyUnit.of(currencyCode)); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/joda/money/DefaultCurrencyUnitDataProvider.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; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.FileNotFoundException; 20 | import java.io.InputStreamReader; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.regex.Pattern; 24 | 25 | /** 26 | * Provider for available currencies using a file. 27 | *
28 | * This reads currencies from various files. 29 | * Firstly it reads the mandatory resource named {@code /org/joda/money/CurencyData.csv}. 30 | * Then it reads the mandatory resource named {@code /org/joda/money/CountryData.csv}. 31 | * These files are located in the joda-money jar file. 32 | *
33 | * Then it reads optional resources named {@code META-INF/org/joda/money/CurencyDataExtension.csv}.
34 | * Then it reads optional resources named {@code META-INF/org/joda/money/CountryDataExtension.csv}.
35 | * These will be read using {@link ClassLoader#getResources(String)}.
36 | * These files may augment or replace data from the first two files.
37 | */
38 | class DefaultCurrencyUnitDataProvider extends CurrencyUnitDataProvider {
39 |
40 | /** Regex format for the money csv line. */
41 | private static final Pattern CURRENCY_REGEX_LINE = Pattern.compile("([A-Z]{3}),(-1|[0-9]{1,3}),(-1|[0-9]|[1-2][0-9]|30) *(#.*)?");
42 | /** Regex format for the country csv line. */
43 | private static final Pattern COUNTRY_REGEX_LINE = Pattern.compile("([A-Z]{2}),([A-Z]{3}) *(#.*)?");
44 |
45 | /**
46 | * Registers all the currencies known by this provider.
47 | *
48 | * @throws Exception if an error occurs
49 | */
50 | @Override
51 | protected void registerCurrencies() throws Exception {
52 | parseCurrencies(loadFromFile("/org/joda/money/CurrencyData.csv"));
53 | parseCountries(loadFromFile("/org/joda/money/CountryData.csv"));
54 | parseCurrencies(loadFromFiles("META-INF/org/joda/money/CurrencyDataExtension.csv"));
55 | parseCountries(loadFromFiles("META-INF/org/joda/money/CountryDataExtension.csv"));
56 | }
57 |
58 | // loads a file
59 | private List
21 | * For example, this exception would be thrown when trying to obtain a
22 | * currency using an unrecognised currency code or locale.
23 | *
24 | * This exception makes no guarantees about immutability or thread-safety.
25 | */
26 | public class IllegalCurrencyException extends IllegalArgumentException {
27 |
28 | /** Serialization lock. */
29 | private static final long serialVersionUID = 1L;
30 |
31 | /**
32 | * Constructor.
33 | *
34 | * @param message the message, may be null
35 | */
36 | public IllegalCurrencyException(String message) {
37 | super(message);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/MoneyUtils.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;
17 |
18 | /**
19 | * Utilities for working with monetary values that handle null.
20 | *
21 | * This utility class contains thread-safe static methods.
22 | */
23 | public final class MoneyUtils {
24 |
25 | /**
26 | * Validates that the object specified is not null.
27 | *
28 | * @param object the object to check, not null
29 | * @throws NullPointerException if the input value is null
30 | */
31 | static void checkNotNull(Object object, String message) {
32 | if (object == null) {
33 | throw new NullPointerException(message);
34 | }
35 | }
36 |
37 | //-----------------------------------------------------------------------
38 | /**
39 | * Private constructor.
40 | */
41 | private MoneyUtils() {
42 | }
43 |
44 | //-----------------------------------------------------------------------
45 | /**
46 | * Checks if the monetary value is zero, treating null as zero.
47 | *
48 | * This method accepts any implementation of {@code BigMoneyProvider}.
49 | *
50 | * @param moneyProvider the money to check, null returns zero
51 | * @return true if the money is null or zero
52 | */
53 | public static boolean isZero(BigMoneyProvider moneyProvider) {
54 | return (moneyProvider == null || moneyProvider.toBigMoney().isZero());
55 | }
56 |
57 | /**
58 | * Checks if the monetary value is positive and non-zero, treating null as zero.
59 | *
60 | * This method accepts any implementation of {@code BigMoneyProvider}.
61 | *
62 | * @param moneyProvider the money to check, null returns false
63 | * @return true if the money is non-null and positive
64 | */
65 | public static boolean isPositive(BigMoneyProvider moneyProvider) {
66 | return (moneyProvider != null && moneyProvider.toBigMoney().isPositive());
67 | }
68 |
69 | /**
70 | * Checks if the monetary value is positive or zero, treating null as zero.
71 | *
72 | * This method accepts any implementation of {@code BigMoneyProvider}.
73 | *
74 | * @param moneyProvider the money to check, null returns true
75 | * @return true if the money is null, zero or positive
76 | */
77 | public static boolean isPositiveOrZero(BigMoneyProvider moneyProvider) {
78 | return (moneyProvider == null || moneyProvider.toBigMoney().isPositiveOrZero());
79 | }
80 |
81 | /**
82 | * Checks if the monetary value is negative and non-zero, treating null as zero.
83 | *
84 | * This method accepts any implementation of {@code BigMoneyProvider}.
85 | *
86 | * @param moneyProvider the money to check, null returns false
87 | * @return true if the money is non-null and negative
88 | */
89 | public static boolean isNegative(BigMoneyProvider moneyProvider) {
90 | return (moneyProvider != null && moneyProvider.toBigMoney().isNegative());
91 | }
92 |
93 | /**
94 | * Checks if the monetary value is negative or zero, treating null as zero.
95 | *
96 | * This method accepts any implementation of {@code BigMoneyProvider}.
97 | *
98 | * @param moneyProvider the money to check, null returns true
99 | * @return true if the money is null, zero or negative
100 | */
101 | public static boolean isNegativeOrZero(BigMoneyProvider moneyProvider) {
102 | return (moneyProvider == null || moneyProvider.toBigMoney().isNegativeOrZero());
103 | }
104 |
105 | //-----------------------------------------------------------------------
106 | /**
107 | * Finds the maximum {@code Money} value, handing null.
108 | *
109 | * This returns the greater of money1 or money2 where null is ignored.
110 | * If both input values are null, then null is returned.
111 | *
112 | * @param money1 the first money instance, null returns money2
113 | * @param money2 the first money instance, null returns money1
114 | * @return the maximum value, null if both inputs are null
115 | * @throws CurrencyMismatchException if the currencies differ
116 | */
117 | public static Money max(Money money1, Money money2) {
118 | if (money1 == null) {
119 | return money2;
120 | }
121 | if (money2 == null) {
122 | return money1;
123 | }
124 | return money1.compareTo(money2) > 0 ? money1 : money2;
125 | }
126 |
127 | /**
128 | * Finds the minimum {@code Money} value, handing null.
129 | *
130 | * This returns the greater of money1 or money2 where null is ignored.
131 | * If both input values are null, then null is returned.
132 | *
133 | * @param money1 the first money instance, null returns money2
134 | * @param money2 the first money instance, null returns money1
135 | * @return the minimum value, null if both inputs are null
136 | * @throws CurrencyMismatchException if the currencies differ
137 | */
138 | public static Money min(Money money1, Money money2) {
139 | if (money1 == null) {
140 | return money2;
141 | }
142 | if (money2 == null) {
143 | return money1;
144 | }
145 | return money1.compareTo(money2) < 0 ? money1 : money2;
146 | }
147 |
148 | //-----------------------------------------------------------------------
149 | /**
150 | * Adds two {@code Money} objects, handling null.
151 | *
152 | * This returns {@code money1 + money2} where null is ignored.
153 | * If both input values are null, then null is returned.
154 | *
155 | * @param money1 the first money instance, null returns money2
156 | * @param money2 the first money instance, null returns money1
157 | * @return the total, where null is ignored, null if both inputs are null
158 | * @throws CurrencyMismatchException if the currencies differ
159 | */
160 | public static Money add(Money money1, Money money2) {
161 | if (money1 == null) {
162 | return money2;
163 | }
164 | if (money2 == null) {
165 | return money1;
166 | }
167 | return money1.plus(money2);
168 | }
169 |
170 | //-----------------------------------------------------------------------
171 | /**
172 | * Subtracts the second {@code Money} from the first, handling null.
173 | *
174 | * This returns {@code money1 - money2} where null is ignored.
175 | * If both input values are null, then null is returned.
176 | *
177 | * @param money1 the first money instance, null treated as zero
178 | * @param money2 the first money instance, null returns money1
179 | * @return the total, where null is ignored, null if both inputs are null
180 | * @throws CurrencyMismatchException if the currencies differ
181 | */
182 | public static Money subtract(Money money1, Money money2) {
183 | if (money2 == null) {
184 | return money1;
185 | }
186 | if (money1 == null) {
187 | return money2.negated();
188 | }
189 | return money1.minus(money2);
190 | }
191 |
192 | //-----------------------------------------------------------------------
193 | /**
194 | * Finds the maximum {@code BigMoney} value, handing null.
195 | *
196 | * This returns the greater of money1 or money2 where null is ignored.
197 | * If both input values are null, then null is returned.
198 | *
199 | * @param money1 the first money instance, null returns money2
200 | * @param money2 the first money instance, null returns money1
201 | * @return the maximum value, null if both inputs are null
202 | * @throws CurrencyMismatchException if the currencies differ
203 | */
204 | public static BigMoney max(BigMoney money1, BigMoney money2) {
205 | if (money1 == null) {
206 | return money2;
207 | }
208 | if (money2 == null) {
209 | return money1;
210 | }
211 | return money1.compareTo(money2) > 0 ? money1 : money2;
212 | }
213 |
214 | /**
215 | * Finds the minimum {@code BigMoney} value, handing null.
216 | *
217 | * This returns the greater of money1 or money2 where null is ignored.
218 | * If both input values are null, then null is returned.
219 | *
220 | * @param money1 the first money instance, null returns money2
221 | * @param money2 the first money instance, null returns money1
222 | * @return the minimum value, null if both inputs are null
223 | * @throws CurrencyMismatchException if the currencies differ
224 | */
225 | public static BigMoney min(BigMoney money1, BigMoney money2) {
226 | if (money1 == null) {
227 | return money2;
228 | }
229 | if (money2 == null) {
230 | return money1;
231 | }
232 | return money1.compareTo(money2) < 0 ? money1 : money2;
233 | }
234 |
235 | //-----------------------------------------------------------------------
236 | /**
237 | * Adds two {@code BigMoney} objects, handling null.
238 | *
239 | * This returns {@code money1 + money2} where null is ignored.
240 | * If both input values are null, then null is returned.
241 | *
242 | * @param money1 the first money instance, null returns money2
243 | * @param money2 the first money instance, null returns money1
244 | * @return the total, where null is ignored, null if both inputs are null
245 | * @throws CurrencyMismatchException if the currencies differ
246 | */
247 | public static BigMoney add(BigMoney money1, BigMoney money2) {
248 | if (money1 == null) {
249 | return money2;
250 | }
251 | if (money2 == null) {
252 | return money1;
253 | }
254 | return money1.plus(money2);
255 | }
256 |
257 | //-----------------------------------------------------------------------
258 | /**
259 | * Subtracts the second {@code BigMoney} from the first, handling null.
260 | *
261 | * This returns {@code money1 - money2} where null is ignored.
262 | * If both input values are null, then null is returned.
263 | *
264 | * @param money1 the first money instance, null treated as zero
265 | * @param money2 the first money instance, null returns money1
266 | * @return the total, where null is ignored, null if both inputs are null
267 | * @throws CurrencyMismatchException if the currencies differ
268 | */
269 | public static BigMoney subtract(BigMoney money1, BigMoney money2) {
270 | if (money2 == null) {
271 | return money1;
272 | }
273 | if (money1 == null) {
274 | return money2.negated();
275 | }
276 | return money1.minus(money2);
277 | }
278 |
279 | }
280 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/Ser.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;
17 |
18 | import java.io.Externalizable;
19 | import java.io.IOException;
20 | import java.io.InvalidClassException;
21 | import java.io.InvalidObjectException;
22 | import java.io.ObjectInput;
23 | import java.io.ObjectOutput;
24 | import java.io.StreamCorruptedException;
25 | import java.math.BigDecimal;
26 | import java.math.BigInteger;
27 |
28 | /**
29 | * A package scoped class used to manage serialization efficiently.
30 | *
31 | * This class is mutable and intended for use by a single thread.
32 | */
33 | final class Ser implements Externalizable {
34 |
35 | /** Type for BigMoney. */
36 | static final byte BIG_MONEY = 'B';
37 | /** Type for Money. */
38 | static final byte MONEY = 'M';
39 | /** Type for CurrencyUnit. */
40 | static final byte CURRENCY_UNIT = 'C'; // not in use yet
41 |
42 | /** The type. */
43 | private byte type;
44 | /** The data object. */
45 | private Object object;
46 |
47 | /**
48 | * Constructor for serialization.
49 | */
50 | public Ser() {
51 | }
52 |
53 | /**
54 | * Constructor for package.
55 | *
56 | * @param type the type
57 | * @param object the object
58 | */
59 | Ser(byte type, Object object) {
60 | this.type = type;
61 | this.object = object;
62 | }
63 |
64 | //-----------------------------------------------------------------------
65 | /**
66 | * Outputs the data.
67 | *
68 | * @serialData One byte type code, then data specific to the type.
69 | * @param out the output stream
70 | * @throws IOException if an error occurs
71 | */
72 | @Override
73 | public void writeExternal(ObjectOutput out) throws IOException {
74 | out.writeByte(type);
75 | switch (type) {
76 | case BIG_MONEY -> {
77 | var obj = (BigMoney) object;
78 | writeBigMoney(out, obj);
79 | }
80 | case MONEY -> {
81 | var obj = (Money) object;
82 | writeBigMoney(out, obj.toBigMoney());
83 | }
84 | case CURRENCY_UNIT -> {
85 | var obj = (CurrencyUnit) object;
86 | writeCurrency(out, obj);
87 | }
88 | default -> throw new InvalidClassException("Joda-Money bug: Serialization broken");
89 | }
90 | }
91 |
92 | private void writeBigMoney(ObjectOutput out, BigMoney obj) throws IOException {
93 | writeCurrency(out, obj.getCurrencyUnit());
94 | var bytes = obj.getAmount().unscaledValue().toByteArray();
95 | out.writeInt(bytes.length);
96 | out.write(bytes);
97 | out.writeInt(obj.getScale());
98 | }
99 |
100 | private void writeCurrency(ObjectOutput out, CurrencyUnit obj) throws IOException {
101 | out.writeUTF(obj.getCode());
102 | out.writeShort(obj.getNumericCode());
103 | out.writeShort(obj.getDecimalPlaces());
104 | }
105 |
106 | /**
107 | * Outputs the data.
108 | *
109 | * @param in the input stream
110 | * @throws IOException if an error occurs
111 | */
112 | @Override
113 | public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
114 | type = in.readByte();
115 | switch (type) {
116 | case BIG_MONEY -> {
117 | object = readBigMoney(in);
118 | }
119 | case MONEY -> {
120 | object = new Money(readBigMoney(in));
121 | }
122 | case CURRENCY_UNIT -> {
123 | object = readCurrency(in);
124 | }
125 | default -> throw new StreamCorruptedException("Serialization input has invalid type");
126 | }
127 | }
128 |
129 | private BigMoney readBigMoney(ObjectInput in) throws IOException {
130 | var currency = readCurrency(in);
131 | var bytes = new byte[in.readInt()];
132 | in.readFully(bytes);
133 | var bd = new BigDecimal(new BigInteger(bytes), in.readInt());
134 | var bigMoney = new BigMoney(currency, bd);
135 | return bigMoney;
136 | }
137 |
138 | private CurrencyUnit readCurrency(ObjectInput in) throws IOException {
139 | var code = in.readUTF();
140 | var singletonCurrency = CurrencyUnit.of(code);
141 | if (singletonCurrency.getNumericCode() != in.readShort()) {
142 | throw new InvalidObjectException("Deserialization found a mismatch in the numeric code for currency " + code);
143 | }
144 | if (singletonCurrency.getDecimalPlaces() != in.readShort()) {
145 | throw new InvalidObjectException("Deserialization found a mismatch in the decimal places for currency " + code);
146 | }
147 | return singletonCurrency;
148 | }
149 |
150 | /**
151 | * Returns the object that will replace this one.
152 | *
153 | * @return the read object, should never be null
154 | */
155 | private Object readResolve() {
156 | return object;
157 | }
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/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 | *
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 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/GroupingStyle.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 | /**
19 | * Defines the style for numeric grouping.
20 | *
21 | * This provides control over the grouping of numbers in formatting.
22 | *
23 | * This class is immutable and thread-safe.
24 | */
25 | public enum GroupingStyle {
26 |
27 | /**
28 | * No grouping occurs.
29 | */
30 | NONE,
31 | /**
32 | * No grouping occurs.
33 | */
34 | FULL,
35 | /**
36 | * Grouping occurs, but only before the decimal point.
37 | */
38 | BEFORE_DECIMAL_POINT;
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/LiteralPrinterParser.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 |
21 | import org.joda.money.BigMoney;
22 |
23 | /**
24 | * Prints and parses a literal.
25 | *
26 | * This class is immutable and thread-safe.
27 | */
28 | final class LiteralPrinterParser implements MoneyPrinter, MoneyParser, Serializable {
29 |
30 | /** Serialization version. */
31 | private static final long serialVersionUID = 1L;
32 |
33 | /** Literal. */
34 | private final String literal;
35 |
36 | /**
37 | * Constructor.
38 | * @param literal the literal text, not null
39 | */
40 | LiteralPrinterParser(String literal) {
41 | this.literal = literal;
42 | }
43 |
44 | //-----------------------------------------------------------------------
45 | @Override
46 | public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
47 | appendable.append(literal);
48 | }
49 |
50 | @Override
51 | public void parse(MoneyParseContext context) {
52 | var endPos = context.getIndex() + literal.length();
53 | if (endPos <= context.getTextLength() &&
54 | context.getTextSubstring(context.getIndex(), endPos).equals(literal)) {
55 | context.setIndex(endPos);
56 | } else {
57 | context.setError();
58 | }
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return "'" + literal + "'";
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/MoneyFormatException.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 |
20 | /**
21 | * Exception thrown during monetary formatting.
22 | *
23 | * This exception makes no guarantees about immutability or thread-safety.
24 | */
25 | public class MoneyFormatException extends RuntimeException {
26 |
27 | /** Serialization lock. */
28 | private static final long serialVersionUID = 87533576L;
29 |
30 | /**
31 | * Constructor taking a message.
32 | *
33 | * @param message the message
34 | */
35 | public MoneyFormatException(String message) {
36 | super(message);
37 | }
38 |
39 | /**
40 | * Constructor taking a message and cause.
41 | *
42 | * @param message the message
43 | * @param cause the exception cause
44 | */
45 | public MoneyFormatException(String message, Throwable cause) {
46 | super(message, cause);
47 | }
48 |
49 | //-----------------------------------------------------------------------
50 | /**
51 | * Checks if the cause of this exception was an IOException, and if so re-throws it
52 | *
53 | * This method is useful if you call a printer with an open stream or
54 | * writer and want to ensure that IOExceptions are not lost.
55 | *
29 | * Instances of {@code MoneyFormatter} can be created by
30 | * {@code MoneyFormatterBuilder}.
31 | *
32 | * This class is immutable and thread-safe.
33 | */
34 | public final class MoneyFormatter implements Serializable {
35 |
36 | /**
37 | * Serialization version.
38 | */
39 | private static final long serialVersionUID = 2385346258L;
40 |
41 | /**
42 | * The locale to use.
43 | */
44 | private final Locale locale;
45 | /**
46 | * The printer/parser.
47 | */
48 | private final MultiPrinterParser printerParser;
49 |
50 | //-----------------------------------------------------------------------
51 | /**
52 | * Validates that the object specified is not null
53 | *
54 | * @param object the object to check, null throws exception
55 | * @param message the message to use in the exception, not null
56 | * @throws NullPointerException if the input value is null
57 | */
58 | static void checkNotNull(Object object, String message) {
59 | if (object == null) {
60 | throw new NullPointerException(message);
61 | }
62 | }
63 |
64 | //-----------------------------------------------------------------------
65 | /**
66 | * Constructor, creating a new formatter.
67 | *
68 | * @param locale the locale to use, not null
69 | * @param printers the printers, not null
70 | * @param parsers the parsers, not null
71 | */
72 | MoneyFormatter(Locale locale, MoneyPrinter[] printers, MoneyParser[] parsers) {
73 | MoneyFormatter.checkNotNull(locale, "Locale must not be null");
74 | MoneyFormatter.checkNotNull(printers, "Printers must not be null");
75 | MoneyFormatter.checkNotNull(parsers, "Parsers must not be null");
76 | if (printers.length != parsers.length) {
77 | throw new IllegalArgumentException("Printers and parsers must match");
78 | }
79 | this.locale = locale;
80 | this.printerParser = new MultiPrinterParser(printers, parsers);
81 | }
82 |
83 | /**
84 | * Constructor, creating a new formatter.
85 | *
86 | * @param locale the locale to use, not null
87 | * @param printerParser the printer/parser, not null
88 | */
89 | private MoneyFormatter(Locale locale, MultiPrinterParser printerParser) {
90 | MoneyFormatter.checkNotNull(locale, "Locale must not be null");
91 | MoneyFormatter.checkNotNull(printerParser, "PrinterParser must not be null");
92 | this.locale = locale;
93 | this.printerParser = printerParser;
94 | }
95 |
96 | //-----------------------------------------------------------------------
97 | /**
98 | * Gets the printer/parser.
99 | *
100 | * @return the printer/parser, never null
101 | */
102 | MultiPrinterParser getPrinterParser() {
103 | return printerParser;
104 | }
105 |
106 | //-----------------------------------------------------------------------
107 | /**
108 | * Gets the locale to use.
109 | *
110 | * @return the locale, never null
111 | */
112 | public Locale getLocale() {
113 | return locale;
114 | }
115 |
116 | /**
117 | * Returns a copy of this instance with the specified locale.
118 | *
119 | * Changing the locale may change the style of output depending on how the
120 | * formatter has been configured.
121 | *
122 | * @param locale the locale, not null
123 | * @return the new instance, never null
124 | */
125 | public MoneyFormatter withLocale(Locale locale) {
126 | checkNotNull(locale, "Locale must not be null");
127 | return new MoneyFormatter(locale, printerParser);
128 | }
129 |
130 | //-----------------------------------------------------------------------
131 | /**
132 | * Checks whether this formatter can print.
133 | *
134 | * If the formatter cannot print, an UnsupportedOperationException will
135 | * be thrown from the print methods.
136 | *
137 | * @return true if the formatter can print
138 | */
139 | public boolean isPrinter() {
140 | return printerParser.isPrinter();
141 | }
142 |
143 | /**
144 | * Checks whether this formatter can parse.
145 | *
146 | * If the formatter cannot parse, an UnsupportedOperationException will
147 | * be thrown from the parse methods.
148 | *
149 | * @return true if the formatter can parse
150 | */
151 | public boolean isParser() {
152 | return printerParser.isParser();
153 | }
154 |
155 | //-----------------------------------------------------------------------
156 | /**
157 | * Prints a monetary value to a {@code String}.
158 | *
159 | * @param moneyProvider the money to print, not null
160 | * @return the string printed using the settings of this formatter
161 | * @throws UnsupportedOperationException if the formatter is unable to print
162 | * @throws MoneyFormatException if there is a problem while printing
163 | */
164 | public String print(BigMoneyProvider moneyProvider) {
165 | var buf = new StringBuilder();
166 | print(buf, moneyProvider);
167 | return buf.toString();
168 | }
169 |
170 | /**
171 | * Prints a monetary value to an {@code Appendable} converting
172 | * any {@code IOException} to a {@code MoneyFormatException}.
173 | *
174 | * Example implementations of {@code Appendable} are {@code StringBuilder},
175 | * {@code StringBuffer} or {@code Writer}. Note that {@code StringBuilder}
176 | * and {@code StringBuffer} never throw an {@code IOException}.
177 | *
178 | * @param appendable the appendable to add to, not null
179 | * @param moneyProvider the money to print, not null
180 | * @throws UnsupportedOperationException if the formatter is unable to print
181 | * @throws MoneyFormatException if there is a problem while printing
182 | */
183 | public void print(Appendable appendable, BigMoneyProvider moneyProvider) {
184 | try {
185 | printIO(appendable, moneyProvider);
186 | } catch (IOException ex) {
187 | throw new MoneyFormatException(ex.getMessage(), ex);
188 | }
189 | }
190 |
191 | /**
192 | * Prints a monetary value to an {@code Appendable} potentially
193 | * throwing an {@code IOException}.
194 | *
195 | * Example implementations of {@code Appendable} are {@code StringBuilder},
196 | * {@code StringBuffer} or {@code Writer}. Note that {@code StringBuilder}
197 | * and {@code StringBuffer} never throw an {@code IOException}.
198 | *
199 | * @param appendable the appendable to add to, not null
200 | * @param moneyProvider the money to print, not null
201 | * @throws UnsupportedOperationException if the formatter is unable to print
202 | * @throws MoneyFormatException if there is a problem while printing
203 | * @throws IOException if an IO error occurs
204 | */
205 | public void printIO(Appendable appendable, BigMoneyProvider moneyProvider) throws IOException {
206 | checkNotNull(moneyProvider, "BigMoneyProvider must not be null");
207 | if (!isPrinter()) {
208 | throw new UnsupportedOperationException("MoneyFomatter has not been configured to be able to print");
209 | }
210 |
211 | var money = BigMoney.of(moneyProvider);
212 | var context = new MoneyPrintContext(locale);
213 | printerParser.print(context, appendable, money);
214 | }
215 |
216 | //-----------------------------------------------------------------------
217 | /**
218 | * Fully parses the text into a {@code BigMoney}.
219 | *
220 | * The parse must complete normally and parse the entire text (currency and amount).
221 | * If the parse completes without reading the entire length of the text, an exception is thrown.
222 | * If any other problem occurs during parsing, an exception is thrown.
223 | *
224 | * @param text the text to parse, not null
225 | * @return the parsed monetary value, never null
226 | * @throws UnsupportedOperationException if the formatter is unable to parse
227 | * @throws MoneyFormatException if there is a problem while parsing
228 | */
229 | public BigMoney parseBigMoney(CharSequence text) {
230 | checkNotNull(text, "Text must not be null");
231 | var result = parse(text, 0);
232 | if (result.isError() || !result.isFullyParsed() || !result.isComplete()) {
233 | var str = (text.length() > 64 ? text.subSequence(0, 64).toString() + "..." : text.toString());
234 | if (result.isError()) {
235 | throw new MoneyFormatException("Text could not be parsed at index " + result.getErrorIndex() + ": " + str);
236 | } else if (!result.isFullyParsed()) {
237 | throw new MoneyFormatException("Unparsed text found at index " + result.getIndex() + ": " + str);
238 | } else {
239 | throw new MoneyFormatException("Parsing did not find both currency and amount: " + str);
240 | }
241 | }
242 | return result.toBigMoney();
243 | }
244 |
245 | /**
246 | * Fully parses the text into a {@code Money} requiring that the parsed
247 | * amount has the correct number of decimal places.
248 | *
249 | * The parse must complete normally and parse the entire text (currency and amount).
250 | * If the parse completes without reading the entire length of the text, an exception is thrown.
251 | * If any other problem occurs during parsing, an exception is thrown.
252 | *
253 | * @param text the text to parse, not null
254 | * @return the parsed monetary value, never null
255 | * @throws UnsupportedOperationException if the formatter is unable to parse
256 | * @throws MoneyFormatException if there is a problem while parsing
257 | * @throws ArithmeticException if the scale of the parsed money exceeds the scale of the currency
258 | */
259 | public Money parseMoney(CharSequence text) {
260 | return parseBigMoney(text).toMoney();
261 | }
262 |
263 | /**
264 | * Parses the text extracting monetary information.
265 | *
266 | * This method parses the input providing low-level access to the parsing state.
267 | * The resulting context contains the parsed text, indicator of error, position
268 | * following the parse and the parsed currency and amount.
269 | * Together, these provide enough information for higher level APIs to use.
270 | *
271 | * @param text the text to parse, not null
272 | * @param startIndex the start index to parse from
273 | * @return the parsed monetary value, null only if the parse results in an error
274 | * @throws IndexOutOfBoundsException if the start index is invalid
275 | * @throws UnsupportedOperationException if this formatter cannot parse
276 | */
277 | public MoneyParseContext parse(CharSequence text, int startIndex) {
278 | checkNotNull(text, "Text must not be null");
279 | if (startIndex < 0 || startIndex > text.length()) {
280 | throw new StringIndexOutOfBoundsException("Invalid start index: " + startIndex);
281 | }
282 | if (!isParser()) {
283 | throw new UnsupportedOperationException("MoneyFomatter has not been configured to be able to parse");
284 | }
285 | var context = new MoneyParseContext(locale, text, startIndex);
286 | printerParser.parse(context);
287 | return context;
288 | }
289 |
290 | //-----------------------------------------------------------------------
291 | /**
292 | * Gets a string summary of the formatter.
293 | *
294 | * @return a string summarising the formatter, never null
295 | */
296 | @Override
297 | public String toString() {
298 | return printerParser.toString();
299 | }
300 |
301 | }
302 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/MoneyFormatterBuilder.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.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Locale;
22 |
23 | import org.joda.money.BigMoney;
24 | import org.joda.money.CurrencyUnit;
25 | import org.joda.money.IllegalCurrencyException;
26 |
27 | /**
28 | * Provides the ability to build a formatter for monetary values.
29 | *
30 | * This class is mutable and intended for use by a single thread.
31 | * A new instance should be created for each use.
32 | * The formatters produced by the builder are immutable and thread-safe.
33 | */
34 | public final class MoneyFormatterBuilder {
35 |
36 | /**
37 | * The printers.
38 | */
39 | private final List
56 | * The format used is {@link MoneyAmountStyle#ASCII_DECIMAL_POINT_GROUP3_COMMA}.
57 | * The amount is the value itself, such as '12.34'.
58 | *
59 | * @return this, for chaining, never null
60 | */
61 | public MoneyFormatterBuilder appendAmount() {
62 | var pp = new AmountPrinterParser(MoneyAmountStyle.ASCII_DECIMAL_POINT_GROUP3_COMMA);
63 | return appendInternal(pp, pp);
64 | }
65 |
66 | /**
67 | * Appends the amount to the builder using a grouped localized format.
68 | *
69 | * The format used is {@link MoneyAmountStyle#LOCALIZED_GROUPING}.
70 | * The amount is the value itself, such as '12.34'.
71 | *
72 | * @return this, for chaining, never null
73 | */
74 | public MoneyFormatterBuilder appendAmountLocalized() {
75 | var pp = new AmountPrinterParser(MoneyAmountStyle.LOCALIZED_GROUPING);
76 | return appendInternal(pp, pp);
77 | }
78 |
79 | /**
80 | * Appends the amount to the builder using the specified amount style.
81 | *
82 | * The amount is the value itself, such as '12.34'.
83 | *
84 | * The amount style allows the formatting of the number to be controlled in detail.
85 | * This includes the characters for positive, negative, decimal, grouping and whether
86 | * to output the absolute or signed amount.
87 | * See {@link MoneyAmountStyle} for more details.
88 | *
89 | * @param style the style to use, not null
90 | * @return this, for chaining, never null
91 | */
92 | public MoneyFormatterBuilder appendAmount(MoneyAmountStyle style) {
93 | MoneyFormatter.checkNotNull(style, "MoneyAmountStyle must not be null");
94 | var pp = new AmountPrinterParser(style);
95 | return appendInternal(pp, pp);
96 | }
97 |
98 | //-----------------------------------------------------------------------
99 | /**
100 | * Appends the currency code to the builder.
101 | *
102 | * The currency code is the three letter ISO code, such as 'GBP'.
103 | *
104 | * @return this, for chaining, never null
105 | */
106 | public MoneyFormatterBuilder appendCurrencyCode() {
107 | return appendInternal(Singletons.CODE, Singletons.CODE);
108 | }
109 |
110 | /**
111 | * Appends the currency code to the builder.
112 | *
113 | * The numeric code is the ISO numeric code, such as '826' and is
114 | * zero padded to three digits.
115 | *
116 | * @return this, for chaining, never null
117 | */
118 | public MoneyFormatterBuilder appendCurrencyNumeric3Code() {
119 | return appendInternal(Singletons.NUMERIC_3_CODE, Singletons.NUMERIC_3_CODE);
120 | }
121 |
122 | /**
123 | * Appends the currency code to the builder.
124 | *
125 | * The numeric code is the ISO numeric code, such as '826'.
126 | *
127 | * @return this, for chaining, never null
128 | */
129 | public MoneyFormatterBuilder appendCurrencyNumericCode() {
130 | return appendInternal(Singletons.NUMERIC_CODE, Singletons.NUMERIC_CODE);
131 | }
132 |
133 | /**
134 | * Appends the localized currency symbol to the builder.
135 | *
136 | * The localized currency symbol is the symbol as chosen by the locale
137 | * of the formatter.
138 | *
139 | * Symbols cannot be parsed.
140 | *
141 | * @return this, for chaining, never null
142 | */
143 | public MoneyFormatterBuilder appendCurrencySymbolLocalized() {
144 | return appendInternal(SingletonPrinters.LOCALIZED_SYMBOL, null);
145 | }
146 |
147 | /**
148 | * Appends a literal to the builder.
149 | *
150 | * The localized currency symbol is the symbol as chosen by the locale
151 | * of the formatter.
152 | *
153 | * @param literal the literal to append, null or empty ignored
154 | * @return this, for chaining, never null
155 | */
156 | public MoneyFormatterBuilder appendLiteral(CharSequence literal) {
157 | if (literal == null || literal.length() == 0) {
158 | return this;
159 | }
160 | var pp = new LiteralPrinterParser(literal.toString());
161 | return appendInternal(pp, pp);
162 | }
163 |
164 | //-----------------------------------------------------------------------
165 | /**
166 | * Appends the printers and parsers from the specified formatter to this builder.
167 | *
168 | * If the specified formatter cannot print, then the the output of this
169 | * builder will be unable to print. If the specified formatter cannot parse,
170 | * then the output of this builder will be unable to parse.
171 | *
172 | * @param formatter the formatter to append, not null
173 | * @return this for chaining, never null
174 | */
175 | public MoneyFormatterBuilder append(MoneyFormatter formatter) {
176 | MoneyFormatter.checkNotNull(formatter, "MoneyFormatter must not be null");
177 | formatter.getPrinterParser().appendTo(this);
178 | return this;
179 | }
180 |
181 | /**
182 | * Appends the specified printer and parser to this builder.
183 | *
184 | * If null is specified then the formatter will be unable to print/parse.
185 | *
186 | * @param printer the printer to append, null makes the formatter unable to print
187 | * @param parser the parser to append, null makes the formatter unable to parse
188 | * @return this for chaining, never null
189 | */
190 | public MoneyFormatterBuilder append(MoneyPrinter printer, MoneyParser parser) {
191 | return appendInternal(printer, parser);
192 | }
193 |
194 | //-----------------------------------------------------------------------
195 | /**
196 | * Appends the specified formatters, one used when the amount is positive,
197 | * and one when the amount is negative.
198 | *
199 | * When printing, the amount is queried and the appropriate formatter is used.
200 | *
201 | * When parsing, each formatter is tried, with the longest successful match,
202 | * or the first match if multiple are successful. If the negative parser is
203 | * matched, the amount returned will be negative no matter what amount is parsed.
204 | *
205 | * A typical use case for this would be to produce a format like
206 | * '{@code ($123)}' for negative amounts and '{@code $123}' for positive amounts.
207 | *
208 | * In order to use this method, it may be necessary to output an unsigned amount.
209 | * This can be achieved using {@link #appendAmount(MoneyAmountStyle)} and
210 | * {@link MoneyAmountStyle#withAbsValue(boolean)}.
211 | *
212 | * @param whenPositiveOrZero the formatter to use when the amount is positive or zero
213 | * @param whenNegative the formatter to use when the amount is negative
214 | * @return this for chaining, never null
215 | */
216 | public MoneyFormatterBuilder appendSigned(MoneyFormatter whenPositiveOrZero, MoneyFormatter whenNegative) {
217 | return appendSigned(whenPositiveOrZero, whenPositiveOrZero, whenNegative);
218 | }
219 |
220 | /**
221 | * Appends the specified formatters, one used when the amount is positive,
222 | * one when the amount is zero and one when the amount is negative.
223 | *
224 | * When printing, the amount is queried and the appropriate formatter is used.
225 | *
226 | * When parsing, each formatter is tried, with the longest successful match,
227 | * or the first match if multiple are successful. If the zero parser is matched,
228 | * the amount returned will be zero no matter what amount is parsed. If the negative
229 | * parser is matched, the amount returned will be negative no matter what amount is parsed.
230 | *
231 | * A typical use case for this would be to produce a format like
232 | * '{@code ($123)}' for negative amounts and '{@code $123}' for positive amounts.
233 | *
234 | * In order to use this method, it may be necessary to output an unsigned amount.
235 | * This can be achieved using {@link #appendAmount(MoneyAmountStyle)} and
236 | * {@link MoneyAmountStyle#withAbsValue(boolean)}.
237 | *
238 | * @param whenPositive the formatter to use when the amount is positive
239 | * @param whenZero the formatter to use when the amount is zero
240 | * @param whenNegative the formatter to use when the amount is negative
241 | * @return this for chaining, never null
242 | */
243 | public MoneyFormatterBuilder appendSigned(MoneyFormatter whenPositive, MoneyFormatter whenZero, MoneyFormatter whenNegative) {
244 | MoneyFormatter.checkNotNull(whenPositive, "MoneyFormatter whenPositive must not be null");
245 | MoneyFormatter.checkNotNull(whenZero, "MoneyFormatter whenZero must not be null");
246 | MoneyFormatter.checkNotNull(whenNegative, "MoneyFormatter whenNegative must not be null");
247 | var pp = new SignedPrinterParser(whenPositive, whenZero, whenNegative);
248 | return appendInternal(pp, pp);
249 | }
250 |
251 | //-----------------------------------------------------------------------
252 | /**
253 | * Appends the specified printer and parser to this builder.
254 | *
255 | * Either the printer or parser must be non-null.
256 | *
257 | * @param printer the printer to append, null makes the formatter unable to print
258 | * @param parser the parser to append, null makes the formatter unable to parse
259 | * @return this for chaining, never null
260 | */
261 | private MoneyFormatterBuilder appendInternal(MoneyPrinter printer, MoneyParser parser) {
262 | printers.add(printer);
263 | parsers.add(parser);
264 | return this;
265 | }
266 |
267 | //-----------------------------------------------------------------------
268 | /**
269 | * Builds the formatter from the builder using the default locale.
270 | *
271 | * Once the builder is in the correct state it must be converted to a
272 | * {@code MoneyFormatter} to be used. Calling this method does not
273 | * change the state of this instance, so it can still be used.
274 | *
275 | * This method uses the default locale within the returned formatter.
276 | * It can be changed by calling {@link MoneyFormatter#withLocale(Locale)}.
277 | *
278 | * @return the formatter built from this builder, never null
279 | */
280 | public MoneyFormatter toFormatter() {
281 | return toFormatter(Locale.getDefault());
282 | }
283 |
284 | /**
285 | * Builds the formatter from the builder setting the locale.
286 | *
287 | * Once the builder is in the correct state it must be converted to a
288 | * {@code MoneyFormatter} to be used. Calling this method does not
289 | * change the state of this instance, so it can still be used.
290 | *
291 | * This method uses the specified locale within the returned formatter.
292 | * It can be changed by calling {@link MoneyFormatter#withLocale(Locale)}.
293 | *
294 | * @param locale the initial locale for the formatter, not null
295 | * @return the formatter built from this builder, never null
296 | */
297 | @SuppressWarnings("cast")
298 | public MoneyFormatter toFormatter(Locale locale) {
299 | MoneyFormatter.checkNotNull(locale, "Locale must not be null");
300 | var printersCopy = printers.toArray(new MoneyPrinter[printers.size()]);
301 | var parsersCopy = parsers.toArray(new MoneyParser[parsers.size()]);
302 | return new MoneyFormatter(locale, printersCopy, parsersCopy);
303 | }
304 |
305 | //-----------------------------------------------------------------------
306 | /**
307 | * Handles the singleton outputs.
308 | */
309 | private static enum Singletons implements MoneyPrinter, MoneyParser {
310 | CODE("${code}") {
311 | @Override
312 | public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
313 | appendable.append(money.getCurrencyUnit().getCode());
314 | }
315 |
316 | @Override
317 | public void parse(MoneyParseContext context) {
318 | var endPos = context.getIndex() + 3;
319 | if (endPos > context.getTextLength()) {
320 | context.setError();
321 | } else {
322 | var code = context.getTextSubstring(context.getIndex(), endPos);
323 | try {
324 | context.setCurrency(CurrencyUnit.of(code));
325 | context.setIndex(endPos);
326 | } catch (IllegalCurrencyException ex) {
327 | context.setError();
328 | }
329 | }
330 | }
331 | },
332 | NUMERIC_3_CODE("${numeric3Code}") {
333 | @Override
334 | public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
335 | appendable.append(money.getCurrencyUnit().getNumeric3Code());
336 | }
337 |
338 | @Override
339 | public void parse(MoneyParseContext context) {
340 | var endPos = context.getIndex() + 3;
341 | if (endPos > context.getTextLength()) {
342 | context.setError();
343 | } else {
344 | var code = context.getTextSubstring(context.getIndex(), endPos);
345 | try {
346 | context.setCurrency(CurrencyUnit.ofNumericCode(code));
347 | context.setIndex(endPos);
348 | } catch (IllegalCurrencyException ex) {
349 | context.setError();
350 | }
351 | }
352 | }
353 | },
354 | NUMERIC_CODE("${numericCode}") {
355 | @Override
356 | public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
357 | appendable.append(Integer.toString(money.getCurrencyUnit().getNumericCode()));
358 | }
359 |
360 | @Override
361 | public void parse(MoneyParseContext context) {
362 | var count = 0;
363 | for (; count < 3 && context.getIndex() + count < context.getTextLength(); count++) {
364 | var ch = context.getText().charAt(context.getIndex() + count);
365 | if (ch < '0' || ch > '9') {
366 | break;
367 | }
368 | }
369 | var endPos = context.getIndex() + count;
370 | var code = context.getTextSubstring(context.getIndex(), endPos);
371 | try {
372 | context.setCurrency(CurrencyUnit.ofNumericCode(code));
373 | context.setIndex(endPos);
374 | } catch (IllegalCurrencyException ex) {
375 | context.setError();
376 | }
377 | }
378 | };
379 |
380 | private final String toString;
381 |
382 | private Singletons(String toString) {
383 | this.toString = toString;
384 | }
385 |
386 | @Override
387 | public String toString() {
388 | return toString;
389 | }
390 | }
391 |
392 | //-----------------------------------------------------------------------
393 | /**
394 | * Handles the singleton outputs.
395 | */
396 | private static enum SingletonPrinters implements MoneyPrinter {
397 | LOCALIZED_SYMBOL;
398 |
399 | @Override
400 | public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
401 | appendable.append(money.getCurrencyUnit().getSymbol(context.getLocale()));
402 | }
403 |
404 | @Override
405 | public String toString() {
406 | return "${symbolLocalized}";
407 | }
408 | }
409 |
410 | }
411 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/MoneyParseContext.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.math.BigDecimal;
19 | import java.text.ParsePosition;
20 | import java.util.Locale;
21 |
22 | import org.joda.money.BigMoney;
23 | import org.joda.money.CurrencyUnit;
24 |
25 | /**
26 | * Context used when parsing money.
27 | *
28 | * This class is mutable and intended for use by a single thread.
29 | * A new instance is created for each parse.
30 | */
31 | public final class MoneyParseContext {
32 |
33 | /**
34 | * The locale to parse using.
35 | */
36 | private Locale locale;
37 | /**
38 | * The text to parse.
39 | */
40 | private CharSequence text;
41 | /**
42 | * The text index.
43 | */
44 | private int textIndex;
45 | /**
46 | * The text error index.
47 | */
48 | private int textErrorIndex = -1;
49 | /**
50 | * The parsed currency.
51 | */
52 | private CurrencyUnit currency;
53 | /**
54 | * The parsed amount.
55 | */
56 | private BigDecimal amount;
57 |
58 | /**
59 | * Constructor.
60 | *
61 | * @param locale the locale, not null
62 | * @param text the text to parse, not null
63 | * @param index the current text index
64 | */
65 | MoneyParseContext(Locale locale, CharSequence text, int index) {
66 | this.locale = locale;
67 | this.text = text;
68 | this.textIndex = index;
69 | }
70 |
71 | /**
72 | * Constructor.
73 | *
74 | * @param locale the locale, not null
75 | * @param text the text to parse, not null
76 | * @param index the current text index
77 | * @param errorIndex the error index
78 | * @param currency the currency
79 | * @param amount the parsed amount
80 | */
81 | MoneyParseContext(Locale locale, CharSequence text, int index, int errorIndex, CurrencyUnit currency, BigDecimal amount) {
82 | this.locale = locale;
83 | this.text = text;
84 | this.textIndex = index;
85 | this.textErrorIndex = errorIndex;
86 | this.currency = currency;
87 | this.amount = amount;
88 | }
89 |
90 | //-----------------------------------------------------------------------
91 | /**
92 | * Gets the locale.
93 | *
94 | * @return the locale, not null
95 | */
96 | public Locale getLocale() {
97 | return locale;
98 | }
99 |
100 | /**
101 | * Sets the locale.
102 | *
103 | * @param locale the locale, not null
104 | */
105 | public void setLocale(Locale locale) {
106 | MoneyFormatter.checkNotNull(locale, "Locale must not be null");
107 | this.locale = locale;
108 | }
109 |
110 | /**
111 | * Gets the text being parsed.
112 | *
113 | * @return the text being parsed, never null
114 | */
115 | public CharSequence getText() {
116 | return text;
117 | }
118 |
119 | /**
120 | * Sets the text.
121 | *
122 | * @param text the text being parsed, not null
123 | */
124 | public void setText(CharSequence text) {
125 | MoneyFormatter.checkNotNull(text, "Text must not be null");
126 | this.text = text;
127 | }
128 |
129 | /**
130 | * Gets the length of the text being parsed.
131 | *
132 | * @return the length of the text being parsed
133 | */
134 | public int getTextLength() {
135 | return text.length();
136 | }
137 |
138 | /**
139 | * Gets a substring of the text being parsed.
140 | *
141 | * @param start the start index
142 | * @param end the end index
143 | * @return the substring, not null
144 | */
145 | public String getTextSubstring(int start, int end) {
146 | return text.subSequence(start, end).toString();
147 | }
148 |
149 | //-----------------------------------------------------------------------
150 | /**
151 | * Gets the current parse position index.
152 | *
153 | * @return the current parse position index
154 | */
155 | public int getIndex() {
156 | return textIndex;
157 | }
158 |
159 | /**
160 | * Sets the current parse position index.
161 | *
162 | * @param index the current parse position index
163 | */
164 | public void setIndex(int index) {
165 | this.textIndex = index;
166 | }
167 |
168 | //-----------------------------------------------------------------------
169 | /**
170 | * Gets the error index.
171 | *
172 | * @return the error index, negative if no error
173 | */
174 | public int getErrorIndex() {
175 | return textErrorIndex;
176 | }
177 |
178 | /**
179 | * Sets the error index.
180 | *
181 | * @param index the error index
182 | */
183 | public void setErrorIndex(int index) {
184 | this.textErrorIndex = index;
185 | }
186 |
187 | /**
188 | * Sets the error index from the current index.
189 | */
190 | public void setError() {
191 | this.textErrorIndex = textIndex;
192 | }
193 |
194 | //-----------------------------------------------------------------------
195 | /**
196 | * Gets the parsed currency.
197 | *
198 | * @return the parsed currency, null if not parsed yet
199 | */
200 | public CurrencyUnit getCurrency() {
201 | return currency;
202 | }
203 |
204 | /**
205 | * Sets the parsed currency.
206 | *
207 | * @param currency the parsed currency, may be null
208 | */
209 | public void setCurrency(CurrencyUnit currency) {
210 | this.currency = currency;
211 | }
212 |
213 | //-----------------------------------------------------------------------
214 | /**
215 | * Gets the parsed amount.
216 | *
217 | * @return the parsed amount, null if not parsed yet
218 | */
219 | public BigDecimal getAmount() {
220 | return amount;
221 | }
222 |
223 | /**
224 | * Sets the parsed currency.
225 | *
226 | * @param amount the parsed amount, may be null
227 | */
228 | public void setAmount(BigDecimal amount) {
229 | this.amount = amount;
230 | }
231 |
232 | //-----------------------------------------------------------------------
233 | /**
234 | * Checks if the parse has found an error.
235 | *
236 | * @return whether a parse error has occurred
237 | */
238 | public boolean isError() {
239 | return textErrorIndex >= 0;
240 | }
241 |
242 | /**
243 | * Checks if the text has been fully parsed such that there is no more text to parse.
244 | *
245 | * @return true if fully parsed
246 | */
247 | public boolean isFullyParsed() {
248 | return textIndex == getTextLength();
249 | }
250 |
251 | /**
252 | * Checks if the context contains a currency and amount suitable for creating
253 | * a monetary value.
254 | *
255 | * @return true if able to create a monetary value
256 | */
257 | public boolean isComplete() {
258 | return currency != null && amount != null;
259 | }
260 |
261 | //-----------------------------------------------------------------------
262 | /**
263 | * Creates a child context.
264 | *
265 | * @return the child context, never null
266 | */
267 | MoneyParseContext createChild() {
268 | return new MoneyParseContext(locale, text, textIndex, textErrorIndex, currency, amount);
269 | }
270 |
271 | /**
272 | * Merges the child context back into this instance.
273 | *
274 | * @param child the child context, not null
275 | */
276 | void mergeChild(MoneyParseContext child) {
277 | setLocale(child.getLocale());
278 | setText(child.getText());
279 | setIndex(child.getIndex());
280 | setErrorIndex(child.getErrorIndex());
281 | setCurrency(child.getCurrency());
282 | setAmount(child.getAmount());
283 | }
284 |
285 | //-----------------------------------------------------------------------
286 | /**
287 | * Converts the indexes to a parse position.
288 | *
289 | * @return the parse position, never null
290 | */
291 | public ParsePosition toParsePosition() {
292 | var pp = new ParsePosition(textIndex);
293 | pp.setErrorIndex(textErrorIndex);
294 | return pp;
295 | }
296 |
297 | /**
298 | * Converts the context to a {@code BigMoney}.
299 | *
300 | * @return the monetary value, never null
301 | * @throws MoneyFormatException if either the currency or amount is missing
302 | */
303 | public BigMoney toBigMoney() {
304 | if (currency == null) {
305 | throw new MoneyFormatException("Cannot convert to BigMoney as no currency found");
306 | }
307 | if (amount == null) {
308 | throw new MoneyFormatException("Cannot convert to BigMoney as no amount found");
309 | }
310 | return BigMoney.of(currency, amount);
311 | }
312 |
313 | }
314 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/MoneyParser.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 | /**
19 | * Parses part of a textual input string of monetary information.
20 | *
21 | * The parser is expected to start parsing at the specified text position
22 | * and match against whatever it represents.
23 | * The parsed result must be stored in the context.
24 | * The context also provides the current parse position which must be updated.
25 | *
26 | * This interface must be implemented with care to ensure other classes operate correctly.
27 | * All instantiable implementations must be thread-safe, and should generally
28 | * be final and immutable.
29 | */
30 | public interface MoneyParser {
31 |
32 | /**
33 | * Parses monetary information using a textual representation.
34 | *
35 | * The text and parse index are stored in the context.
36 | * The parsed data and updated index is also stored in the context.
37 | *
38 | * Implementations should avoid throwing exceptions and use the error index
39 | * in the context instead to record the problem.
40 | * The context can be assumed to not be in error on entry to this method.
41 | *
42 | * The context is not a thread-safe object and a new instance will be created
43 | * for each parse. The context must not be stored in an instance variable
44 | * or shared with any other threads.
45 | *
46 | * @param context the context to use and parse into, not null
47 | */
48 | public abstract void parse(MoneyParseContext context);
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/MoneyPrintContext.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.util.Locale;
19 |
20 | /**
21 | * Context used when printing money.
22 | *
23 | * This class is mutable and intended for use by a single thread.
24 | * A new instance is created for each parse.
25 | */
26 | public final class MoneyPrintContext {
27 |
28 | /**
29 | * The locale to print using.
30 | */
31 | private Locale locale;
32 |
33 | /**
34 | * Constructor.
35 | *
36 | * @param locale the locale, not null
37 | */
38 | MoneyPrintContext(Locale locale) {
39 | MoneyFormatter.checkNotNull(locale, "Locale must not be null");
40 | this.locale = locale;
41 | }
42 |
43 | //-----------------------------------------------------------------------
44 | /**
45 | * Gets the locale.
46 | *
47 | * @return the locale, never null
48 | */
49 | public Locale getLocale() {
50 | return locale;
51 | }
52 |
53 | /**
54 | * Sets the locale.
55 | *
56 | * @param locale the locale, not null
57 | */
58 | public void setLocale(Locale locale) {
59 | MoneyFormatter.checkNotNull(locale, "Locale must not be null");
60 | this.locale = locale;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/MoneyPrinter.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 |
20 | import org.joda.money.BigMoney;
21 |
22 | /**
23 | * Prints part of a monetary value to the output appendable.
24 | *
25 | * The printer may print any part, or the whole, of the input {@link BigMoney}.
26 | * Typically, a complete print is constructed from a number of smaller printers
27 | * that have been combined using {@link MoneyFormatterBuilder}.
28 | *
29 | * This interface must be implemented with care to ensure other classes operate correctly.
30 | * All instantiable implementations must be thread-safe, and should generally
31 | * be final and immutable.
32 | */
33 | public interface MoneyPrinter {
34 |
35 | /**
36 | * Prints part of a monetary value to the output appendable.
37 | *
38 | * The implementation determines what to append, which may be some or all
39 | * of the data held in the {@code BigMoney}.
40 | *
41 | * The context is not a thread-safe object and a new instance will be created
42 | * for each print. The context must not be stored in an instance variable
43 | * or shared with any other threads.
44 | *
45 | * @param context the context being used, not null
46 | * @param appendable the appendable to add to, not null
47 | * @param money the money to print, not null
48 | * @throws MoneyFormatException if there is a problem while printing
49 | * @throws IOException if an IO exception occurs
50 | */
51 | public abstract void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException;
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/MultiPrinterParser.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.util.Arrays;
21 |
22 | import org.joda.money.BigMoney;
23 |
24 | /**
25 | * Prints and parses multiple printers/parsers.
26 | *
27 | * This class is immutable and thread-safe.
28 | */
29 | final class MultiPrinterParser implements MoneyPrinter, MoneyParser, Serializable {
30 |
31 | /** Serialization version. */
32 | private static final long serialVersionUID = 1L;
33 |
34 | /**
35 | * The printers.
36 | */
37 | private final MoneyPrinter[] printers;
38 | /**
39 | * The parsers.
40 | */
41 | private final MoneyParser[] parsers;
42 |
43 | /**
44 | * Constructor.
45 | * @param printers the printers, not null
46 | */
47 | MultiPrinterParser(MoneyPrinter[] printers, MoneyParser[] parsers) {
48 | this.printers = printers;
49 | this.parsers = parsers;
50 | }
51 |
52 | //-----------------------------------------------------------------------
53 | boolean isPrinter() {
54 | return !Arrays.asList(printers).contains(null);
55 | }
56 |
57 | boolean isParser() {
58 | return !Arrays.asList(parsers).contains(null);
59 | }
60 |
61 | void appendTo(MoneyFormatterBuilder builder) {
62 | for (var i = 0; i < printers.length; i++) {
63 | builder.append(printers[i], parsers[i]);
64 | }
65 | }
66 |
67 | //-----------------------------------------------------------------------
68 | @Override
69 | public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
70 | for (MoneyPrinter printer : printers) {
71 | printer.print(context, appendable, money);
72 | }
73 | }
74 |
75 | @Override
76 | public void parse(MoneyParseContext context) {
77 | for (MoneyParser parser : parsers) {
78 | parser.parse(context);
79 | if (context.isError()) {
80 | break;
81 | }
82 | }
83 | }
84 |
85 | @Override
86 | public String toString() {
87 | var buf1 = new StringBuilder();
88 | if (isPrinter()) {
89 | for (MoneyPrinter printer : printers) {
90 | buf1.append(printer.toString());
91 | }
92 | }
93 | var buf2 = new StringBuilder();
94 | if (isParser()) {
95 | for (MoneyParser parser : parsers) {
96 | buf2.append(parser.toString());
97 | }
98 | }
99 | var str1 = buf1.toString();
100 | var str2 = buf2.toString();
101 | if (isPrinter() && !isParser()) {
102 | return str1;
103 | } else if (isParser() && !isPrinter()) {
104 | return str2;
105 | } else if (str1.equals(str2)) {
106 | return str1;
107 | } else {
108 | return str1 + ":" + str2;
109 | }
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/SignedPrinterParser.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 using delegated formatters, one for positive and one for megative.
26 | *
27 | * This class is immutable and thread-safe.
28 | */
29 | final class SignedPrinterParser implements MoneyPrinter, MoneyParser, Serializable {
30 |
31 | /** Serialization version. */
32 | private static final long serialVersionUID = 1L;
33 |
34 | /** The formatter to use when positive. */
35 | private final MoneyFormatter whenPositive;
36 | /** The formatter to use when zero. */
37 | private final MoneyFormatter whenZero;
38 | /** The formatter to use when negative. */
39 | private final MoneyFormatter whenNegative;
40 |
41 | /**
42 | * Constructor.
43 | * @param whenPositive the formatter to use when the amount is positive
44 | * @param whenZero the formatter to use when the amount is zero
45 | * @param whenNegative the formatter to use when the amount is positive
46 | */
47 | SignedPrinterParser(MoneyFormatter whenPositive, MoneyFormatter whenZero, MoneyFormatter whenNegative) {
48 | this.whenPositive = whenPositive;
49 | this.whenZero = whenZero;
50 | this.whenNegative = whenNegative;
51 | }
52 |
53 | //-----------------------------------------------------------------------
54 | @Override
55 | public void print(MoneyPrintContext context, Appendable appendable, BigMoney money) throws IOException {
56 | var fmt = (money.isZero() ? whenZero : money.isPositive() ? whenPositive : whenNegative);
57 | fmt.getPrinterParser().print(context, appendable, money);
58 | }
59 |
60 | @Override
61 | public void parse(MoneyParseContext context) {
62 | var positiveContext = context.createChild();
63 | whenPositive.getPrinterParser().parse(positiveContext);
64 | var zeroContext = context.createChild();
65 | whenZero.getPrinterParser().parse(zeroContext);
66 | var negativeContext = context.createChild();
67 | whenNegative.getPrinterParser().parse(negativeContext);
68 | var best = (MoneyParseContext) null;
69 | if (!positiveContext.isError()) {
70 | best = positiveContext;
71 | }
72 | if (!zeroContext.isError()) {
73 | if (best == null || zeroContext.getIndex() > best.getIndex()) {
74 | best = zeroContext;
75 | }
76 | }
77 | if (!negativeContext.isError()) {
78 | if (best == null || negativeContext.getIndex() > best.getIndex()) {
79 | best = negativeContext;
80 | }
81 | }
82 | if (best == null) {
83 | context.setError();
84 | } else {
85 | context.mergeChild(best);
86 | if (best == zeroContext) {
87 | if (context.getAmount() == null || context.getAmount().compareTo(BigDecimal.ZERO) != 0) {
88 | context.setAmount(BigDecimal.ZERO);
89 | }
90 | } else if (best == negativeContext && context.getAmount().compareTo(BigDecimal.ZERO) > 0) {
91 | context.setAmount(context.getAmount().negate());
92 | }
93 | }
94 | }
95 |
96 | @Override
97 | public String toString() {
98 | return "PositiveZeroNegative(" + whenPositive + "," + whenZero + "," + whenNegative + ")";
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/proguard/jodamoney.pro:
--------------------------------------------------------------------------------
1 | # Keep the DefaultCurrencyUnitDataProvider class and its constructor
2 | # as it is accessed via reflection in the static initializer of CurrencyUnit
3 | -keep class org.joda.money.DefaultCurrencyUnitDataProvider {
4 |
56 | * try {
57 | * printer.print(writer, money);
58 | * } catch (CalendricalFormatException ex) {
59 | * ex.rethrowIOException();
60 | * // if code reaches here exception was caused by issues other than IO
61 | * }
62 | *
63 | * Note that calling this method will re-throw the original IOException,
64 | * causing this MoneyFormatException to be lost.
65 | *
66 | * @throws IOException if the cause of this exception is an IOException
67 | */
68 | public void rethrowIOException() throws IOException {
69 | if (getCause() instanceof IOException) {
70 | throw (IOException) getCause();
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/org/joda/money/format/MoneyFormatter.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.util.Locale;
21 |
22 | import org.joda.money.BigMoney;
23 | import org.joda.money.BigMoneyProvider;
24 | import org.joda.money.Money;
25 |
26 | /**
27 | * Formats instances of money to and from a String.
28 | *
20 | Tidelift’s security response team coordinates patches for new breaking security vulnerabilities and alerts
21 | immediately through a private channel, so your software supply chain is always secure.
22 |
23 | * **Licensing verification and indemnification**
24 | Tidelift verifies license information to enable easy policy enforcement and adds intellectual property
25 | indemnification to cover creators and users in case something goes wrong. You always have a 100% up-to-date
26 | bill of materials for your dependencies to share with your legal team, customers, or partners.
27 |
28 | * **Maintenance and code improvement**
29 | Tidelift ensures the software you rely on keeps working as long as you need it to work.
30 | Your managed dependencies are actively maintained and we recruit additional maintainers where required.
31 |
32 | * **Package selection and version guidance**
33 | We help you choose the best open source packages from the start—and then guide you through updates to stay on
34 | the best releases as new issues arise.
35 |
36 | * **Roadmap input**
37 | Take a seat at the table with the creators behind the software you use. Tidelift’s participating maintainers
38 | earn more income as their software is used by more subscribers, so they’re interested in knowing what you need.
39 |
40 | * **Tooling and cloud integration**
41 | Tidelift works with GitHub, GitLab, BitBucket, and more.
42 | We support every cloud platform (and other deployment targets, too).
43 |
44 | The end result? All of the capabilities you expect from commercial-grade software, for the full breadth
45 | of open source you use. That means less time grappling with esoteric open source trivia, and more
46 | time building your own applications—and your business.
47 |
48 |
49 |
50 |
51 | [1]: https://tidelift.com/subscription/pkg/maven-org-joda-joda-money?utm_source=maven-org-joda-joda-money&utm_medium=referral&utm_campaign=enterprise
52 | [2]: https://tidelift.com/subscription/request-a-demo?utm_source=maven-org-joda-joda-money&utm_medium=referral&utm_campaign=enterprise
53 |
--------------------------------------------------------------------------------
/src/site/markdown/index.md:
--------------------------------------------------------------------------------
1 | ## About
2 |
3 | **Joda-Money** provides a library of classes to store amounts of money.
4 |
5 | The JDK provides a standard currency class, but not a standard representation of money.
6 | Joda-Money fills this gap, providing the value types to represent money.
7 |
8 | Joda-Money is licensed under the business-friendly [Apache 2.0 licence](licenses.html).
9 |
10 |
11 | ## Features
12 |
13 | A selection of key features:
14 |
15 | * `CurrencyUnit` - representing a currency
16 | * `Money` - a fixed precision monetary value type
17 | * `BigMoney` - a variable precision monetary type
18 | * A customizable formatter
19 |
20 |
21 | ## Documentation
22 |
23 | Various documentation is available:
24 |
25 | * The helpful [user guide](userguide.html)
26 | * The [Javadoc](apidocs/index.html)
27 | * The [change notes](changes-report.html) for each release
28 | * The [GitHub](https://github.com/JodaOrg/joda-money) source repository
29 |
30 |
31 | ---
32 |
33 | ## Why Joda Money?
34 |
35 | Joda-Money provides simple value types, representing currency and money.
36 |
37 | The project does not provide, nor is it intended to provide, monetary algorithms beyond the most basic and obvious.
38 | This is because the requirements for these algorithms vary widely between domains.
39 | This library is intended to act as the base layer, providing classes that should be in the JDK.
40 |
41 | As a flavour of Joda-Money, here is some example code:
42 |
43 |
44 | // create a monetary value
45 | Money money = Money.parse("USD 23.87");
46 |
47 | // add another amount with safe double conversion
48 | CurrencyUnit usd = CurrencyUnit.of("USD");
49 | money = money.plus(Money.of(usd, 12.43d));
50 |
51 | // subtracts an amount in dollars
52 | money = money.minusMajor(2);
53 |
54 | // multiplies by 3.5 with rounding
55 | money = money.multipliedBy(3.5d, RoundingMode.DOWN);
56 |
57 | // compare two amounts
58 | boolean bigAmount = money.isGreaterThan(dailyWage);
59 |
60 | // convert to GBP using a supplied rate
61 | BigDecimal conversionRate = ...; // obtained from code outside Joda-Money
62 | Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
63 |
64 | // use a BigMoney for more complex calculations where scale matters
65 | BigMoney moneyCalc = money.toBigMoney();
66 |
67 |
68 |
69 | ---
70 |
71 | ## Releases
72 |
73 | The 2.x branch (v2.0.2) is compatible with Java SE 21 or later.
74 |
75 | The 1.x branch (v1.0.6) is compatible with Java SE 8 or later.
76 |
77 | v2.x releases are compatible with v1.x releases - except for the Java SE version and `module-info.class` file.
78 |
79 | Joda-Money has no mandatory dependencies.
80 | There is a *compile-time* dependency on [Joda-Convert](https://www.joda.org/joda-convert/),
81 | but this is not required at runtime thanks to the magic of annotations.
82 |
83 | Available in [Maven Central](https://search.maven.org/search?q=g:org.joda%20AND%20a:joda-money&core=gav).
84 | [GitHub release bundles](https://github.com/JodaOrg/joda-money/releases).
85 |
86 | ```xml
87 |