├── .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 | 5 | 6 | 7 | sonatype-joda-staging 8 | ${env.OSSRH_USERNAME} 9 | ${env.OSSRH_TOKEN} 10 | 11 | 12 | github 13 | ${env.GITHUB_TOKEN} 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | branches: 9 | - 'main' 10 | schedule: 11 | - cron: '41 19 * * 2' 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | build: 18 | permissions: 19 | security-events: write # for github/codeql-action 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | with: 25 | token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 26 | fetch-tags: true 27 | 28 | - name: Setup git 29 | run: | 30 | git config --global user.name "Stephen Colebourne (CI)" 31 | git config --global user.email "scolebourne@joda.org" 32 | 33 | - name: Set up JDK 34 | uses: actions/setup-java@v4 35 | with: 36 | java-version: 21 37 | distribution: 'temurin' 38 | cache: 'maven' 39 | 40 | - name: Maven version 41 | run: | 42 | mkdir -p ./.mvn 43 | echo "-e" >> ./.mvn/maven.config 44 | echo "-B" >> ./.mvn/maven.config 45 | echo "-ntp" >> ./.mvn/maven.config 46 | echo "-DtrimStackTrace=false" >> ./.mvn/maven.config 47 | echo "--settings" >> ./.mvn/maven.config 48 | echo "$( pwd )/.github/maven-settings.xml" >> ./.mvn/maven.config 49 | mvn --version 50 | mkdir -p target 51 | 52 | #------------------------------------------------------------------------ 53 | - name: Initialize CodeQL 54 | uses: github/codeql-action/init@v3 55 | with: 56 | languages: java 57 | 58 | - name: Maven build 59 | run: | 60 | mvn install -Dextended-test 61 | 62 | - name: Perform CodeQL Analysis 63 | uses: github/codeql-action/analyze@v3 64 | 65 | - name: Maven site 66 | run: | 67 | mvn clean site 68 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'release*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 19 | ref: "main" 20 | fetch-tags: true 21 | 22 | - name: Setup git 23 | run: | 24 | git config --global user.name "Stephen Colebourne (CI)" 25 | git config --global user.email "scolebourne@joda.org" 26 | 27 | - name: Set up JDK 28 | uses: actions/setup-java@v4 29 | with: 30 | java-version: 21 31 | distribution: 'temurin' 32 | cache: 'maven' 33 | 34 | - name: Maven version 35 | run: | 36 | mkdir -p ./.mvn 37 | echo "-e" >> ./.mvn/maven.config 38 | echo "-B" >> ./.mvn/maven.config 39 | echo "-ntp" >> ./.mvn/maven.config 40 | echo "-DtrimStackTrace=false" >> ./.mvn/maven.config 41 | echo "--settings" >> ./.mvn/maven.config 42 | echo "$( pwd )/.github/maven-settings.xml" >> ./.mvn/maven.config 43 | mvn --version 44 | mkdir -p target 45 | 46 | #------------------------------------------------------------------------ 47 | - name: Maven install 48 | run: | 49 | mvn clean install 50 | 51 | - name: Maven release 52 | env: 53 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 54 | OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }} 55 | MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }} 56 | MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} 57 | GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 58 | run: | 59 | mvn release:clean release:prepare release:perform 60 | 61 | - name: Update website 62 | run: | 63 | git tag websiterelease 64 | git push origin websiterelease 65 | 66 | - name: Delete release tag 67 | if: "always()" 68 | run: | 69 | git tag --delete "${GITHUB_REF_NAME}" || true 70 | git push --delete origin "${GITHUB_REF_NAME}" || true 71 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: Website 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'website*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 19 | fetch-tags: true 20 | 21 | - name: Setup git 22 | run: | 23 | git config --global user.name "Stephen Colebourne (CI)" 24 | git config --global user.email "scolebourne@joda.org" 25 | 26 | - name: Set up JDK 27 | uses: actions/setup-java@v4 28 | with: 29 | java-version: 21 30 | distribution: 'temurin' 31 | cache: 'maven' 32 | 33 | - name: Maven version 34 | run: | 35 | mkdir -p ./.mvn 36 | echo "-e" >> ./.mvn/maven.config 37 | echo "-B" >> ./.mvn/maven.config 38 | echo "-ntp" >> ./.mvn/maven.config 39 | echo "-DtrimStackTrace=false" >> ./.mvn/maven.config 40 | echo "--settings" >> ./.mvn/maven.config 41 | echo "$( pwd )/.github/maven-settings.xml" >> ./.mvn/maven.config 42 | mvn --version 43 | mkdir -p target 44 | 45 | #------------------------------------------------------------------------ 46 | - name: Maven site 47 | run: | 48 | mvn install site 49 | 50 | - name: Checkout website 51 | uses: actions/checkout@v4 52 | with: 53 | token: ${{ secrets.PERSONAL_GITHUB_TOKEN }} 54 | repository: JodaOrg/jodaorg.github.io 55 | path: target/jodaorg.github.io 56 | ref: "main" 57 | 58 | - name: Update website 59 | run: | 60 | cd target/jodaorg.github.io 61 | git status 62 | 63 | rm -rf joda-money/ 64 | cp -R ../site joda-money/ 65 | 66 | git add -A 67 | git status 68 | git commit --message "Update joda-money from CI: $GITHUB_ACTION" 69 | 70 | git push origin main 71 | 72 | - name: Delete website tag 73 | run: | 74 | git tag --delete "${GITHUB_REF_NAME}" || true 75 | git push --delete origin "${GITHUB_REF_NAME}" || true 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /target/ 3 | *.log 4 | /tests/ 5 | .checkstyle 6 | .classpath 7 | .project 8 | /.settings/ 9 | /nbproject/ 10 | .idea 11 | *.iml 12 | /test-output/ 13 | /src/test/java/module-info.java 14 | *.DS_Store 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Joda Money 2 | Copyright 2009-present Stephen Colebourne 3 | 4 | This product includes software developed by 5 | Joda.org (https://www.joda.org/). 6 | -------------------------------------------------------------------------------- /Notes.txt: -------------------------------------------------------------------------------- 1 | JScience - MoneyAmount, based on Javaloution classes 2 | http://jscience.org/api/org/jscience/economics/money/MoneyAmount.html 3 | 4 | JMoney - long directly 5 | https://jmoney.svn.sourceforge.net/svnroot/jmoney/trunk/net.sf.jmoney/src/net/sf/jmoney/model2/ 6 | 7 | TimeAndMoney - Money class with currency scale (BigDecimal + JDK Currency) 8 | http://timeandmoney.svn.sourceforge.net/viewvc/timeandmoney/timeandmoney/trunk/src/main/java/com/domainlanguage/money/Money.java?revision=340&view=markup 9 | 10 | Money from Tom Gibara - Small library with good approach to calculation 11 | http://www.tomgibara.com/projects/money/ 12 | 13 | JCash - BigDecimal directly 14 | http://jcash.cvs.sourceforge.net/viewvc/jcash/jcash/src/jcash/ 15 | 16 | Eurobudget - double directly 17 | http://eurobudget.cvs.sourceforge.net/viewvc/eurobudget/eurobudget/src/com/pjsofts/eurobudget/beans/ 18 | 19 | Java practices - Class based on BigDecimal 20 | http://www.javapractices.com/topic/TopicAction.do?Id=13 21 | 22 | Grails Currency Plugin - Class based on BigDecimal 23 | https://github.com/ricardojmendez/grails-currencies/blob/master/grails-app/domain/Money.groovy 24 | 25 | OpenGamma CurrencyAmount - Class based on double (its an estimate) 26 | https://github.com/OpenGamma/OG-Platform/blob/master/projects/OG-Util/src/main/java/com/opengamma/util/money/CurrencyAmount.java 27 | 28 | Roguewave - BigDecimal or Double, mutable, extends to currency conversion 29 | http://www.roguewave.com/portals/0/products/legacy-hpp/docs/mnyapi/Package-com.roguewave.money.currency.v1-0.html 30 | 31 | Design document 32 | http://www.objectivelogic.com/resources/Java%20and%20Monetary%20Data/Java%20and%20Monetary%20Data.pdf 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Joda-Money 2 | ---------- 3 | 4 | Joda-Money provides a library of classes to store amounts of money. 5 | 6 | Joda-Money does not provide, nor is it intended to provide, monetary algorithms beyond the most basic and obvious. 7 | This is because the requirements for these algorithms vary widely between domains. 8 | This library is intended to act as the base layer, providing classes that should be in the JDK. 9 | 10 | As a flavour of Joda-Money, here's some example code: 11 | 12 | ```java 13 | // create a monetary value 14 | Money money = Money.parse("USD 23.87"); 15 | 16 | // add another amount with safe double conversion 17 | CurrencyUnit usd = CurrencyUnit.of("USD"); 18 | money = money.plus(Money.of(usd, 12.43d)); 19 | 20 | // subtracts an amount in dollars 21 | money = money.minusMajor(2); 22 | 23 | // multiplies by 3.5 with rounding 24 | money = money.multipliedBy(3.5d, RoundingMode.DOWN); 25 | 26 | // compare two amounts 27 | boolean bigAmount = money.isGreaterThan(dailyWage); 28 | 29 | // convert to GBP using a supplied rate 30 | BigDecimal conversionRate = ...; // obtained from code outside Joda-Money 31 | Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_EVEN); 32 | 33 | // use a BigMoney for more complex calculations where scale matters 34 | BigMoney moneyCalc = money.toBigMoney(); 35 | ``` 36 | 37 | Users are reminded that this software, like all open source software, is provided 38 | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND. 39 | 40 | Joda-Money is licensed under the business-friendly [Apache 2.0 licence](https://www.joda.org/joda-money/licenses.html). 41 | 42 | 43 | ### Documentation 44 | Various documentation is available: 45 | 46 | * The [home page](https://www.joda.org/joda-money/) 47 | * The helpful [user guide](https://www.joda.org/joda-money/userguide.html) 48 | * The [Javadoc](https://www.joda.org/joda-money/apidocs/index.html) 49 | * The change notes for the [releases](https://www.joda.org/joda-money/changes-report.html) 50 | 51 | 52 | ### Releases 53 | The 2.x branch is compatible with Java SE 21 or later. 54 | 55 | The 1.x branch is compatible with Java SE 8 or later. 56 | 57 | v2.x releases are compatible with v1.x releases - except for the Java SE version and `module-info.class` file. 58 | 59 | Joda-Money has no mandatory dependencies. 60 | There is a *compile-time* dependency on [Joda-Convert](https://www.joda.org/joda-convert/), 61 | but this is not required at runtime thanks to the magic of annotations. 62 | 63 | Available in the [Maven Central repository](https://search.maven.org/search?q=g:org.joda%20AND%20a:joda-money&core=gav) 64 | 65 | ![Tidelift dependency check](https://tidelift.com/badges/github/JodaOrg/joda-money) 66 | 67 | 68 | ### For enterprise 69 | Available as part of the Tidelift Subscription. 70 | 71 | Joda and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. 72 | 73 | If you want the flexibility of open source and the confidence of commercial-grade software, this is for you. 74 | 75 | [Learn more](https://tidelift.com/subscription/pkg/maven-org-joda-joda-money?utm_source=maven-org-joda-joda-money&utm_medium=github) 76 | 77 | 78 | ### Support 79 | Please use [Stack Overflow](https://stackoverflow.com/questions/tagged/joda-money) for general usage questions. 80 | GitHub [issues](https://github.com/JodaOrg/joda-money/issues) and [pull requests](https://github.com/JodaOrg/joda-money/pulls) 81 | should be used when you want to help advance the project. 82 | 83 | Any donations to support the project are accepted via [OpenCollective](https://opencollective.com/joda). 84 | 85 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). 86 | Tidelift will coordinate the fix and disclosure. 87 | 88 | 89 | ### Release process 90 | 91 | * Update version (index.md, changes.xml) 92 | * Commit and push 93 | * `git push origin HEAD:refs/tags/release` 94 | * Code and Website will be built and released by GitHub Actions 95 | 96 | Release from local: 97 | 98 | * Turn off gpg "bc" signer 99 | * `mvn clean release:clean release:prepare release:perform` 100 | -------------------------------------------------------------------------------- /RELEASE-NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | Joda-Money 3 | ================================================ 4 | Joda-Money is a monetary library that fills the gap in the JDK 5 | by providing a Money class and associated formatting. 6 | 7 | The release runs on JDK 21 or later. 8 | 9 | See https://www.joda.org/joda-money/changes-report.html for changes 10 | 11 | Joda-Money is licensed under the business-friendly Apache License Version 2. 12 | This is the same license as all of Apache, plus other open source projects such as Spring. 13 | 14 | 15 | Feedback 16 | -------- 17 | Feedback is best received using GitHub issues and Pull Requests. 18 | https://github.com/JodaOrg/joda-money/ 19 | 20 | The Joda team 21 | -------------------------------------------------------------------------------- /src/changes/changes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Changes 6 | Stephen Colebourne 7 | 8 | 9 | 10 | 11 | 12 | 13 | Fix proguard rule. 14 | 15 | 16 | 17 | 18 | Change currency for CW and SX from ANG to XCG. (ISO 4217 Amendment 176). 19 | This change is effective from 31st March 2025. 20 | 21 | 22 | Include proguard rule. 23 | 24 | 25 | 26 | 27 | Major version based on Java SE 21. 28 | Intended to be fully compatible with v1.x. 29 | 30 | 31 | 32 | 33 | Change currency for CW and SX from ANG to XCG. (ISO 4217 Amendment 176). 34 | This change is effective from 31st March 2025. 35 | 36 | 37 | 38 | 39 | Change currency for ZW from ZWL to ZWG. (ISO 4217 Amendment 177). 40 | 41 | 42 | Return 'XXX' as the currency symbol for 'XXX' on Java 21. 43 | The data in Java was altered to use ¤ but it is more consistent for Joda-Money to continue using 'XXX'. 44 | 45 | 46 | 47 | 48 | Add isGreaterThanOrEqual() and isLessThanOrEqual() to money classes. 49 | 50 | 51 | Workaround issue with BigDecimal.stripTrailingZeros() on early Android versions. 52 | 53 | 54 | 55 | 56 | Change currency HRK to EUR. (ISO 4217 Amendment 174) 57 | Note that the official switch from HRK to EUR occurs on 2023-01-01. 58 | You may want to delay updating this dependency until then. 59 | 60 | 61 | Switch master to main. 62 | 63 | 64 | Switch LGTM to CodeQL. 65 | 66 | 67 | 68 | 69 | Change currency SLL to SLE. (ISO 4217 Amendment 171) 70 | 71 | 72 | Add Tidelift commercial support and security policy. 73 | 74 | 75 | Enhance null checks. 76 | 77 | 78 | 79 | 80 | Ensure that localized format handles no grouping in JDK data. 81 | 82 | 83 | Change currency VEF to VES. 84 | 85 | 86 | 87 | 88 | Move project to Java 8. 89 | 90 | 91 | Add module-info. 92 | Fixes #81. 93 | 94 | 95 | Remove deprecated methods. 96 | 97 | 98 | 99 | 100 | Change currency data files. 101 | Files are now `CurrencyData.csv` and `CountryData.csv`. 102 | These can be overridden in `CurrencyDataExtension.csv` and `CountryDataExtension.csv`. 103 | Add historic EUR currencies. 104 | 105 | 106 | Update and redesign build to support Java 9. 107 | 108 | 109 | Allow currencies to have 30 decimal places. 110 | 111 | 112 | 113 | 114 | Update currency codes to latest ISO-4217. 115 | 116 | 117 | Update currencies for BY, LT, LV, SS. 118 | Fixes #68, #70, #72. 119 | 120 | 121 | Deprecate some less-than-ideal methods. 122 | 123 | 124 | 125 | 126 | Allow currency conversion to the same currency if the conversion factor is one. 127 | 128 | 129 | Add support for Indian number formatting (Lakh/Crore). 130 | MoneyAmountStyle extended grouping size provides enough power to output this. 131 | 132 | 133 | Add support for different signed formats. 134 | Add appendSigned() to builder, allowing different formats for positive, zero and negative. 135 | 136 | 137 | Add support for absolute values. 138 | Using MoneyAmountStyle, absolute (unsigned) amounts can be output. 139 | 140 | 141 | Remove deprecated methods. 142 | 143 | 144 | Change ZMW to 2 decimal places. 145 | 146 | 147 | Alter parameter names of CurrencyUnit constructor to better support tools that make use of the names. 148 | 149 | 150 | 151 | 152 | Ensure that a negative scale is not visible in BigMoney. 153 | This should have no impact on most users, but does change the semantics of certain methods. 154 | The rationale is to ensure that the toString() matches equals() and the internal state. 155 | Previously, BigMoney.ofScale(GBP, 100, 0) and BigMoney.ofScale(GBP, 1, -2) were not equal 156 | but had the same toString() (in violation of VALJO rules). 157 | Now, they are both normalized to the first form - no negative scale. 158 | 159 | 160 | Change COP to 2 decimal places. 161 | 162 | 163 | Fix bugs in formatting with MoneyAmountStyle. 164 | 165 | 166 | Change ZMK currency to ZMW. 167 | 168 | 169 | Change CNY to 2 decimal places. 170 | 171 | 172 | Close resources after loading. 173 | 174 | 175 | 176 | 177 | Allow zero to many spaces when using standard parse method. 178 | 179 | 180 | Ensure validation of string currency code. 181 | Fixes #31, #36. 182 | 183 | 184 | Fix Javadoc of BigMoney.withScale. 185 | 186 | 187 | Home page at GitHub. 188 | 189 | 190 | 191 | 192 | New option for grouping only before the decimal point [16] 193 | 194 | 195 | Allow registration of currencies to override existing [24] 196 | 197 | 198 | Allow registration of currencies by application code [17] 199 | 200 | 201 | Change to use m2e Maven Eclipse. 202 | 203 | 204 | Allow config file to support same range of values accepted by code [18/19/21] 205 | 206 | 207 | 208 | 209 | Change to requiring JDK 1.6. 210 | 211 | 212 | Allow a second currency data file to enhance those in the base file [9,13] 213 | 214 | 215 | 216 | 217 | Fix printing of grouped negative numbers [10] 218 | 219 | 220 | Change LBP currency to 2dp [7] 221 | 222 | 223 | Relax checking for decimal places 224 | 225 | 226 | Move Estonia to the Euro [2] 227 | 228 | 229 | Add old Russian currency [1] 230 | 231 | 232 | 233 | 234 | Major updates and changes. 235 | 236 | 237 | 238 | 239 | Initial version. 240 | 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /src/main/assembly/dist.xml: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | 4 | tar.gz 5 | zip 6 | 7 | ${artifactId}-${version} 8 | 9 | 10 | 11 | checkstyle.xml 12 | checkstyle-suppressions.xml 13 | LICENSE.txt 14 | NOTICE.txt 15 | pom.xml 16 | RELEASE-NOTES.txt 17 | 18 | 19 | 20 | src 21 | 22 | 23 | target 24 | 25 | 26 | *.jar 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/main/java/module-info.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 | 17 | /** 18 | * Joda-Money provides a library of classes to store amounts of money. 19 | *

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 loadFromFile(String fileName) throws Exception { 60 | try (var in = getClass().getResourceAsStream(fileName)) { 61 | if (in == null) { 62 | throw new FileNotFoundException("Data file " + fileName + " not found"); 63 | } 64 | try (var reader = new BufferedReader(new InputStreamReader(in, "UTF-8"))) { 65 | String line; 66 | List content = new ArrayList<>(); 67 | while ((line = reader.readLine()) != null) { 68 | content.add(line); 69 | } 70 | return content; 71 | } 72 | } 73 | } 74 | 75 | // loads a file 76 | private List loadFromFiles(String fileName) throws Exception { 77 | List content = new ArrayList<>(); 78 | var en = getClass().getClassLoader().getResources(fileName); 79 | while (en.hasMoreElements()) { 80 | var url = en.nextElement(); 81 | try (var in = url.openStream()) { 82 | try (var reader = new BufferedReader(new InputStreamReader(in, "UTF-8"))) { 83 | String line; 84 | while ((line = reader.readLine()) != null) { 85 | content.add(line); 86 | } 87 | } 88 | } 89 | } 90 | return content; 91 | } 92 | 93 | // parse the currencies 94 | private void parseCurrencies(List content) throws Exception { 95 | for (String line : content) { 96 | var matcher = CURRENCY_REGEX_LINE.matcher(line); 97 | if (matcher.matches()) { 98 | var currencyCode = matcher.group(1); 99 | var numericCode = Integer.parseInt(matcher.group(2)); 100 | var digits = Integer.parseInt(matcher.group(3)); 101 | registerCurrency(currencyCode, numericCode, digits); 102 | } 103 | } 104 | } 105 | 106 | // parse the countries 107 | private void parseCountries(List content) throws Exception { 108 | for (String line : content) { 109 | var matcher = COUNTRY_REGEX_LINE.matcher(line); 110 | if (matcher.matches()) { 111 | var countryCode = matcher.group(1); 112 | var currencyCode = matcher.group(2); 113 | registerCountry(countryCode, currencyCode); 114 | } 115 | } 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/org/joda/money/IllegalCurrencyException.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 the requested currency is illegal. 20 | *

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 | *

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 | *

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 printers = new ArrayList<>(); 40 | /** 41 | * The parsers. 42 | */ 43 | private final List parsers = new ArrayList<>(); 44 | 45 | //----------------------------------------------------------------------- 46 | /** 47 | * Constructor, creating a new empty builder. 48 | */ 49 | public MoneyFormatterBuilder() { 50 | } 51 | 52 | //----------------------------------------------------------------------- 53 | /** 54 | * Appends the amount to the builder using a standard format. 55 | *

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 | (); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/resources/org/joda/money/CountryData.csv: -------------------------------------------------------------------------------- 1 | #Country,Currency 2 | # The term "Country" is used as per ISO 3166-1 3 | # "Codes for the representation of names of countries and their subdivisions – Part 1: Country codes" 4 | AD,EUR 5 | AE,AED 6 | AF,AFN 7 | AG,XCD 8 | AI,XCD 9 | AL,ALL 10 | AM,AMD 11 | AO,AOA 12 | # AQ, Antarctica 13 | AR,ARS 14 | AS,USD 15 | AT,EUR 16 | AU,AUD 17 | AW,AWG 18 | AX,EUR 19 | AZ,AZN 20 | BA,BAM 21 | BB,BBD 22 | BD,BDT 23 | BE,EUR 24 | BF,XOF 25 | BG,BGN 26 | BH,BHD 27 | BI,BIF 28 | BJ,XOF 29 | BL,EUR 30 | BM,BMD 31 | BN,BND 32 | BO,BOB # Special: BOV 33 | BQ,USD 34 | BR,BRL 35 | BS,BSD 36 | BT,BTN 37 | BV,NOK 38 | BW,BWP 39 | BY,BYN 40 | BZ,BZD 41 | CA,CAD 42 | CC,AUD 43 | CD,CDF 44 | CF,XAF 45 | CG,XAF 46 | CH,CHF # Special: CHE/CHW 47 | CI,XOF 48 | CK,NZD 49 | CL,CLP # Special: CLF 50 | CM,XAF 51 | CN,CNY 52 | CO,COP # Special: COU 53 | CR,CRC 54 | CU,CUP # Special: CUC 55 | CV,CVE 56 | CW,XCG 57 | CX,AUD 58 | CY,EUR 59 | CZ,CZK 60 | DE,EUR 61 | DJ,DJF 62 | DK,DKK 63 | DM,XCD 64 | DO,DOP 65 | DZ,DZD 66 | EC,USD 67 | EE,EUR 68 | EG,EGP 69 | EH,MAD 70 | ER,ERN 71 | ES,EUR 72 | ET,ETB 73 | FI,EUR 74 | FJ,FJD 75 | FK,FKP 76 | FM,USD 77 | FO,DKK 78 | FR,EUR 79 | GA,XAF 80 | GB,GBP 81 | GD,XCD 82 | GE,GEL 83 | GF,EUR 84 | GG,GBP 85 | GH,GHS 86 | GI,GIP 87 | GL,DKK 88 | GM,GMD 89 | GN,GNF 90 | GP,EUR 91 | GQ,XAF 92 | GR,EUR 93 | GS,FKP # ISO list as unclear 94 | GT,GTQ 95 | GU,USD 96 | GW,XOF 97 | GY,GYD 98 | HK,HKD 99 | HM,AUD 100 | HN,HNL 101 | HR,EUR 102 | HT,HTG 103 | HU,HUF 104 | ID,IDR 105 | IE,EUR 106 | IL,ILS 107 | IM,GBP 108 | IN,INR 109 | IO,USD # GBP officially 110 | IQ,IQD 111 | IR,IRR 112 | IS,ISK 113 | IT,EUR 114 | JE,GBP 115 | JM,JMD 116 | JO,JOD 117 | JP,JPY 118 | KE,KES 119 | KG,KGS 120 | KH,KHR 121 | KI,AUD 122 | KM,KMF 123 | KN,XCD 124 | KP,KPW 125 | KR,KRW 126 | KW,KWD 127 | KY,KYD 128 | KZ,KZT 129 | LA,LAK 130 | LB,LBP 131 | LC,XCD 132 | LI,CHF 133 | LK,LKR 134 | LR,LRD 135 | LS,LSL 136 | LT,EUR 137 | LU,EUR 138 | LV,EUR 139 | LY,LYD 140 | MA,MAD 141 | MC,EUR 142 | MD,MDL 143 | ME,EUR 144 | MF,EUR 145 | MG,MGA 146 | MH,USD 147 | MK,MKD 148 | ML,XOF 149 | MM,MMK 150 | MN,MNT 151 | MO,MOP 152 | MP,USD 153 | MQ,EUR 154 | MR,MRU 155 | MS,XCD 156 | MT,EUR 157 | MU,MUR 158 | MV,MVR 159 | MW,MWK 160 | MX,MXN # Special: MXV 161 | MY,MYR 162 | MZ,MZN 163 | NA,NAD 164 | NC,XPF 165 | NE,XOF 166 | NF,AUD 167 | NG,NGN 168 | NI,NIO 169 | NL,EUR 170 | NO,NOK 171 | NP,NPR 172 | NR,AUD 173 | NU,NZD 174 | NZ,NZD 175 | OM,OMR 176 | PA,PAB 177 | PE,PEN 178 | PF,XPF 179 | PG,PGK 180 | PH,PHP 181 | PK,PKR 182 | PL,PLN 183 | PM,EUR 184 | PN,NZD 185 | PR,USD 186 | PS,ILS # appears to be most common 187 | PT,EUR 188 | PW,USD 189 | PY,PYG 190 | QA,QAR 191 | RE,EUR 192 | RO,RON 193 | RS,RSD 194 | RU,RUB 195 | RW,RWF 196 | SA,SAR 197 | SB,SBD 198 | SC,SCR 199 | SD,SDG 200 | SE,SEK 201 | SG,SGD 202 | SH,SHP 203 | SI,EUR 204 | SJ,NOK 205 | SK,EUR 206 | SL,SLE 207 | SM,EUR 208 | SN,XOF 209 | SO,SOS 210 | SR,SRD 211 | SS,SSP 212 | ST,STN 213 | SV,SVC 214 | SX,XCG 215 | SY,SYP 216 | SZ,SZL 217 | TC,USD 218 | TD,XAF 219 | TF,EUR 220 | TG,XOF 221 | TH,THB 222 | TJ,TJS 223 | TK,NZD 224 | TL,USD 225 | TM,TMT 226 | TN,TND 227 | TO,TOP 228 | TR,TRY 229 | TT,TTD 230 | TV,AUD 231 | TW,TWD 232 | TZ,TZS 233 | UA,UAH 234 | UG,UGX 235 | UM,USD 236 | US,USD # Special: USN 237 | UY,UYU # Special: UYI 238 | UZ,UZS 239 | VA,EUR 240 | VC,XCD 241 | VE,VES 242 | VG,USD 243 | VI,USD 244 | VN,VND 245 | VU,VUV 246 | WF,XPF 247 | WS,WST 248 | YE,YER 249 | YT,EUR 250 | ZA,ZAR 251 | ZM,ZMW 252 | ZW,ZWG 253 | 254 | # Exceptional, only including EU 255 | # AC,SHP 256 | # CP,EUR 257 | # DG,USD # GBP officially 258 | # EA,EUR 259 | EU,EUR 260 | # EZ, # Eurozone 261 | # FX,EUR 262 | # IC,EUR 263 | # SU, # Soviet Union 264 | # TA,GBP 265 | # UK, # see GB 266 | # UN, # United Nations 267 | 268 | # Transitional, deprecated/old 269 | # AN,ANG # see CW/SX 270 | # BU, # see MM 271 | # CS, # see CZ/SK 272 | # NT, # see IQ/SA 273 | # SF, # see FI 274 | # TP, # see TL 275 | # YU, # Yugoslavia 276 | # ZR, # see CD 277 | -------------------------------------------------------------------------------- /src/main/resources/org/joda/money/CurrencyData.csv: -------------------------------------------------------------------------------- 1 | #Code,Numeric,DecPlaces 2 | # Some historic currencies are included, others are not 3 | # PRs are welcome to include old currencies, so long as they used to be officially part of ISO-4217 4 | # Old currencies must be commented as such, see below for examples 5 | ADP,20,0 # Old, now EUR 6 | AED,784,2 7 | AFN,971,2 8 | ALL,8,2 9 | AMD,51,2 10 | ANG,532,2 # Old, now XCG 11 | AOA,973,2 12 | ARS,32,2 13 | ATS,40,2 # Old, now EUR 14 | AUD,36,2 15 | AWG,533,2 16 | AZN,944,2 17 | BAM,977,2 18 | BBD,52,2 19 | BDT,50,2 20 | BEF,56,0 # Old, now EUR 21 | BGN,975,2 22 | BHD,48,3 23 | BIF,108,0 24 | BMD,60,2 25 | BND,96,2 26 | BOB,68,2 27 | BOV,984,2 # FundsCode 28 | BRL,986,2 29 | BSD,44,2 30 | BTN,64,2 31 | BWP,72,2 32 | BYN,933,2 33 | BYR,974,0 # Old, now BYN 34 | BZD,84,2 35 | CAD,124,2 36 | CDF,976,2 37 | CHE,947,2 # FundsCode 38 | CHF,756,2 39 | CHW,948,2 # FundsCode 40 | CLF,990,4 # FundsCode 41 | CLP,152,0 42 | CNY,156,2 43 | COP,170,2 44 | COU,970,2 # FundsCode 45 | CRC,188,2 46 | CUC,931,2 # Special 47 | CUP,192,2 48 | CVE,132,2 49 | CYP,196,2 # Old, now EUR 50 | CZK,203,2 51 | DEM,276,2 # Old, now EUR 52 | DJF,262,0 53 | DKK,208,2 54 | DOP,214,2 55 | DZD,12,2 56 | EEK,233,2 # Old, now EUR 57 | EGP,818,2 58 | ERN,232,2 59 | ESP,724,0 # Old, now EUR 60 | ETB,230,2 61 | EUR,978,2 62 | FIM,246,2 # Old, now EUR 63 | FJD,242,2 64 | FKP,238,2 65 | FRF,250,2 # Old, now EUR 66 | GBP,826,2 67 | GEL,981,2 68 | GHS,936,2 69 | GIP,292,2 70 | GMD,270,2 71 | GNF,324,0 72 | GRD,300,0 # Old, now EUR 73 | GTQ,320,2 74 | GYD,328,2 75 | HKD,344,2 76 | HNL,340,2 77 | HRK,191,2 # Old, now EUR 78 | HTG,332,2 79 | HUF,348,2 80 | IDR,360,2 81 | IEP,372,2 # Old, now EUR 82 | ILS,376,2 83 | INR,356,2 84 | IQD,368,3 85 | IRR,364,2 86 | ISK,352,0 87 | ITL,380,0 # Old, now EUR 88 | JMD,388,2 89 | JOD,400,3 90 | JPY,392,0 91 | KES,404,2 92 | KGS,417,2 93 | KHR,116,2 94 | KMF,174,0 95 | KPW,408,2 96 | KRW,410,0 97 | KWD,414,3 98 | KYD,136,2 99 | KZT,398,2 100 | LAK,418,2 101 | LBP,422,2 102 | LKR,144,2 103 | LRD,430,2 104 | LSL,426,2 105 | LTL,440,2 # Old, now EUR 106 | LUF,442,0 # Old, now EUR 107 | LVL,428,2 # Old, now EUR 108 | LYD,434,3 109 | MAD,504,2 110 | MDL,498,2 111 | MGA,969,2 112 | MKD,807,2 113 | MMK,104,2 114 | MNT,496,2 115 | MOP,446,2 116 | MRO,478,2 # Old, now MRU 117 | MRU,929,2 118 | MTL,470,2 # Old, now EUR 119 | MUR,480,2 120 | MVR,462,2 121 | MWK,454,2 122 | MXN,484,2 123 | MXV,979,2 # FundsCode 124 | MYR,458,2 125 | MZN,943,2 126 | NAD,516,2 127 | NGN,566,2 128 | NIO,558,2 129 | NLG,528,2 # Old, now EUR 130 | NOK,578,2 131 | NPR,524,2 132 | NZD,554,2 133 | OMR,512,3 134 | PAB,590,2 135 | PEN,604,2 136 | PGK,598,2 137 | PHP,608,2 138 | PKR,586,2 139 | PLN,985,2 140 | PYG,600,0 141 | PTE,620,0 # Old, now EUR 142 | QAR,634,2 143 | RON,946,2 144 | RSD,941,2 145 | RUB,643,2 146 | RUR,810,2 # Old, now RUB 147 | RWF,646,0 148 | SAR,682,2 149 | SBD,90,2 150 | SCR,690,2 151 | SDG,938,2 152 | SEK,752,2 153 | SGD,702,2 154 | SHP,654,2 155 | SIT,705,2 # Old, now EUR 156 | SKK,703,2 # Old, now EUR 157 | SLE,925,2 158 | SLL,694,2 # Old, now SLE 159 | SOS,706,2 160 | SRD,968,2 161 | SSP,728,2 162 | STD,678,2 # Old, now STN 163 | STN,930,2 164 | SVC,222,2 165 | SYP,760,2 166 | SZL,748,2 167 | THB,764,2 168 | TJS,972,2 169 | TMT,934,2 170 | TND,788,3 171 | TOP,776,2 172 | TRY,949,2 173 | TTD,780,2 174 | TWD,901,2 175 | TZS,834,2 176 | UAH,980,2 177 | UGX,800,0 178 | USD,840,2 179 | USN,997,2 # FundsCode 180 | UYI,940,0 # FundsCode 181 | UYU,858,2 182 | UZS,860,2 183 | VEF,937,2 # Old, now VES 184 | VES,928,2 185 | VND,704,0 186 | VUV,548,0 187 | WST,882,2 188 | XAF,950,0 189 | XAG,961,-1 190 | XAU,959,-1 191 | XBA,955,-1 192 | XBB,956,-1 193 | XBC,957,-1 194 | XBD,958,-1 195 | XCD,951,2 196 | XCG,532,2 197 | XDR,960,-1 198 | XEU,954,-1 # Old, now EUR 199 | XFU,-1,-1 # Old, now EUR 200 | XOF,952,0 201 | XPD,964,-1 202 | XPF,953,0 203 | XPT,962,-1 204 | XSU,994,-1 205 | XTS,963,-1 206 | XUA,965,-1 207 | XXX,999,-1 208 | YER,886,2 209 | ZAR,710,2 210 | ZMW,967,2 211 | ZWG,924,2 212 | ZWL,932,2 # Old, now ZWG 213 | -------------------------------------------------------------------------------- /src/site/markdown/enterprise.md: -------------------------------------------------------------------------------- 1 | ## Joda-Money for Enterprise 2 | 3 | ### Available as part of the Tidelift Subscription 4 | 5 | **Tidelift** is working with the maintainers of **Joda-Money** and thousands of other open source projects to deliver 6 | commercial support and maintenance for the open source dependencies you use to build your applications. 7 | Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. 8 | 9 | 10 | 11 | 12 | ### Enterprise-ready open source software - managed for you 13 | 14 | The Tidelift Subscription is a managed open source subscription for application dependencies covering millions 15 | of open source projects across JavaScript, Python, Java, PHP, Ruby, .NET, and more. 16 | 17 | Your subscription includes: 18 | 19 | * **Security updates**
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 | 88 | org.joda 89 | joda-money 90 | 2.0.2 91 | 92 | ``` 93 | 94 | Java module name: `org.joda.money`. 95 | 96 | --- 97 | 98 | ### For Enterprise 99 | 100 | [Available as part of the Tidelift Subscription](https://tidelift.com/subscription/pkg/maven-org-joda-joda-money?utm_source=maven-org-joda-joda-money&utm_medium=referral&utm_campaign=enterprise). 101 | 102 | Joda and the maintainers of thousands of other packages are working with Tidelift to deliver one 103 | enterprise subscription that covers all of the open source you use. 104 | 105 | If you want the flexibility of open source and the confidence of commercial-grade software, this is for you. 106 | [Learn more](https://tidelift.com/subscription/pkg/maven-org-joda-joda-money?utm_source=maven-org-joda-joda-money&utm_medium=referral&utm_campaign=enterprise). 107 | 108 | 109 | ### Support 110 | 111 | Please use [Stack Overflow](https://stackoverflow.com/questions/tagged/joda-money) for general usage questions. 112 | GitHub [issues](https://github.com/JodaOrg/joda-money/issues) and [pull requests](https://github.com/JodaOrg/joda-money/pulls) 113 | should be used when you want to help advance the project. 114 | 115 | Any donations to support the project are accepted via [OpenCollective](https://opencollective.com/joda). 116 | 117 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). 118 | Tidelift will coordinate the fix and disclosure. 119 | -------------------------------------------------------------------------------- /src/site/resources/css/site.css: -------------------------------------------------------------------------------- 1 | /* Fix broken definition that causes hyperlinks to break */ 2 | h1[id]:before, 3 | h2[id]:before, 4 | h3[id]:before, 5 | h4[id]:before, 6 | h5[id]:before, 7 | h6[id]:before, 8 | a[name]:before { 9 | height:0px; 10 | margin:0px; 11 | } 12 | /* Blacker text */ 13 | body { 14 | color: #222; 15 | } 16 | code, pre { 17 | color: #444; 18 | } 19 | .main-body p b { 20 | font-weight: bold; 21 | color: #722; 22 | } 23 | .dropdown-menu>li>a { 24 | color: #666; 25 | } 26 | /* Sidebar had too much padding at the top */ 27 | .well { 28 | padding-top: 6px; 29 | padding-bottom: 36px; 30 | } 31 | /* Font Awesome icons by CSS as markdown class is stripped */ 32 | h2 i { 33 | display: inline-block; 34 | font: normal normal normal 14px/1 FontAwesome; 35 | font-size: inherit; 36 | text-rendering: auto; 37 | -webkit-font-smoothing: antialiased; 38 | -moz-osx-font-smoothing: grayscale; 39 | } 40 | h2#About i:before { 41 | content: "\f015"; 42 | } 43 | h2#Features i:before { 44 | content: "\f0d0"; 45 | } 46 | h2#Documentation i:before { 47 | content: "\f02d"; 48 | } 49 | h2#Releases i:before { 50 | content: "\f02c"; 51 | } 52 | h2#Why_Joda_Money i:before { 53 | content: "\f19c"; 54 | } 55 | h2#Rationale i:before { 56 | content: "\f0eb"; 57 | } 58 | 59 | /* Enterprise page */ 60 | h2#Joda-Money_for_Enterprise~div > p:last-child { 61 | text-align: center; 62 | margin-top: 1.1em; 63 | margin-bottom: 1.2em; 64 | } 65 | button.btn-learnmore, button.btn-requestdemo { 66 | width: 14em; 67 | font-size: 1.2em; 68 | text-align: center; 69 | padding-top: 0.3em; 70 | padding-bottom: 0.3em; 71 | border-radius: 3px; 72 | border-style: solid; 73 | } 74 | button.btn-learnmore { 75 | color: #f6914d; 76 | background-color: white; 77 | border-color: #f6914d; 78 | } 79 | button.btn-learnmore a { 80 | color: #f6914d; 81 | } 82 | button.btn-requestdemo { 83 | color: white; 84 | background-color: #f6914d; 85 | border-color: #f6914d; 86 | } 87 | button.btn-requestdemo a { 88 | color: white; 89 | } 90 | -------------------------------------------------------------------------------- /src/site/resources/download.html: -------------------------------------------------------------------------------- 1 | 2 | OpenGamma 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UA-1425975-4 6 | 7 | 8 | org.joda.external 9 | reflow-maven-skin 10 | 1.2 11 | 12 | 13 | 14 | 15 | false 16 | true 17 | github 18 | false 19 | bootswatch-cosmo 20 | 21 | Joda-Money 22 | index.html 23 | 24 | Documentation|Releases|Development|Joda|For Enterprise 25 | 26 | Documentation 27 | Releases 28 | Development 29 | Reports 30 | 31 | 34 | false 35 | false 36 | false 37 | true 38 | 39 | 40 | 41 | 3 42 | 1 43 | 44 | 45 | 46 | Home 47 | false 48 | 49 | 50 | false 51 | 52 | 53 | false 54 | 55 | 56 | false 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 66 | ]]> 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/site/xdoc/userguide.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User guide 7 | Stephen Colebourne 8 | 9 | 10 | 11 | 12 |
13 |

14 | Joda-Money is a small Java library providing classes to store, format and parse amounts of money. 15 | This is a brief user guide to the features available. 16 |

17 |
18 | 19 | 20 |
21 |

22 | There are two main classes which are both based on BigDecimal: 23 |

    24 |
  • Money - an amount in a single currency where the decimal places is determined by the currency
  • 25 |
  • BigMoney - an amount in a single currency where there is no restriction on the scale
  • 26 |
27 |

28 |

29 | For example, the currencies of US dollars, Euros and British Pounds all use 2 decimal places, 30 | thus a Money instance for any of these currencies will have 2 decimal places. 31 | By contrast, the Japanese Yen has 0 decimal places, and thus any Money instance with that 32 | currency will have 0 decimal places. 33 |

34 |

35 | Instances of BigMoney may have a scale that is positive or zero. 36 | Negative scales are normalized to zero. 37 |

38 |

39 | Conversion between a Money and a BigMoney can be performed using the methods 40 | toBigMoney() and toMoney(). The latter optionally takes a rounding mode to 41 | handle the case where information must be lost. 42 |

43 |

44 | Both classes implement the BigMoneyProvider interface. 45 | This allows many different classes, including user supplied classes, to interoperate with Joda-Money. 46 | In application code, the best advice is to use the concrete types. 47 | However, utility and framework code will typically use the interface. 48 | The formatting code is a good example of this. 49 |

50 |
51 | 52 | 53 |
54 |

55 | Joda-Money includes its own currency class as the implementation in the JDK is too restrictive. 56 | The data for the Joda-Money class is provided by two configuration files. 57 |

58 |

59 | The file org/joda/money/CurrencyData.csv holds a basic list of valid currencies. 60 | The columns are as follows: 61 |

62 |

63 |

    64 |
  • ISO-4217 currency code - three letters, mandatory
  • 65 |
  • ISO-4217 numeric code - from 1 to 999, set to -1 if no numeric code
  • 66 |
  • Number of decimal places in common usage - the supplied data is based on various sources
  • 67 |
68 |

69 |

70 | If you place a file on the classpath named META-INF/org/joda/money/CurrencyDataExtension.csv 71 | then the data it contains will augment and replace the base data. 72 | The extension file has the same format as the base file. 73 |

74 |

75 | The file org/joda/money/CountryData.csv holds a mapping from ISO-3166 country code to currency. 76 | The columns are as follows: 77 |

78 |

79 |

    80 |
  • ISO-3166-1 country code - two letters, mandatory
  • 81 |
  • ISO-4217 currency code - three letters, mandatory
  • 82 |
83 |

84 |

85 | If you place a file on the classpath named META-INF/org/joda/money/CountryDataExtension.csv 86 | then the data it contains will augment and replace the base data. 87 | The extension file has the same format as the base file. 88 |

89 |

90 | (Note that in previous versions, these data files had different names and different formats.) 91 |

92 |
93 | 94 | 95 |
96 |

97 | Formatting is based around the MoneyFormatterBuilder class. 98 | The builder is used to create a suitable format which is then converted to an immutable MoneyFormatter instance. 99 | The format may contain localized aspects which can be use by calling withLocale() on the formatter. 100 | This returns a new formatter as it is immutable. 101 |

102 |

103 | The symbol information for formatting is currently provided by the JDK. 104 |

105 |

106 |
107 |

108 |

109 |
110 |

111 |
112 | 113 | 114 |
115 | -------------------------------------------------------------------------------- /src/test-whitebox/module-info.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 | 17 | /** 18 | * Joda-Money test module. 19 | */ 20 | open module org.joda.money { 21 | 22 | // mandatory for testing 23 | requires org.joda.convert; 24 | 25 | // all packages are exported 26 | exports org.joda.money; 27 | exports org.joda.money.format; 28 | 29 | requires transitive org.junit.jupiter.api; 30 | requires transitive org.junit.jupiter.engine; 31 | requires transitive org.junit.jupiter.params; 32 | requires transitive org.assertj.core; 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/TestCurrencyMismatchException.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 static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | /** 23 | * Test CurrencyMismatchException. 24 | */ 25 | class TestCurrencyMismatchException { 26 | 27 | private static final CurrencyUnit GBP = CurrencyUnit.of("GBP"); 28 | private static final CurrencyUnit EUR = CurrencyUnit.of("EUR"); 29 | 30 | //----------------------------------------------------------------------- 31 | // new (CurrencyUnit,CurrencyUnit) 32 | //----------------------------------------------------------------------- 33 | @Test 34 | void test_new_GBPEUR() { 35 | var test = new CurrencyMismatchException(GBP, EUR); 36 | assertThat(test.getMessage()).isEqualTo("Currencies differ: GBP/EUR"); 37 | assertThat(test.getCause()).isNull(); 38 | assertThat(test.getFirstCurrency()).isEqualTo(GBP); 39 | assertThat(test.getSecondCurrency()).isEqualTo(EUR); 40 | } 41 | 42 | @Test 43 | void test_new_nullEUR() { 44 | var test = new CurrencyMismatchException(null, EUR); 45 | assertThat(test.getMessage()).isEqualTo("Currencies differ: null/EUR"); 46 | assertThat(test.getCause()).isNull(); 47 | assertThat(test.getFirstCurrency()).isNull(); 48 | assertThat(test.getSecondCurrency()).isEqualTo(EUR); 49 | } 50 | 51 | @Test 52 | void test_new_GBPnull() { 53 | var test = new CurrencyMismatchException(GBP, null); 54 | assertThat(test.getMessage()).isEqualTo("Currencies differ: GBP/null"); 55 | assertThat(test.getCause()).isNull(); 56 | assertThat(test.getFirstCurrency()).isEqualTo(GBP); 57 | assertThat(test.getSecondCurrency()).isNull(); 58 | } 59 | 60 | @Test 61 | void test_new_nullnull() { 62 | var test = new CurrencyMismatchException(null, null); 63 | assertThat(test.getMessage()).isEqualTo("Currencies differ: null/null"); 64 | assertThat(test.getCause()).isNull(); 65 | assertThat(test.getFirstCurrency()).isNull(); 66 | assertThat(test.getSecondCurrency()).isNull(); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/TestCurrencyUnitExtension.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 static org.assertj.core.api.Assertions.assertThat; 19 | import static org.assertj.core.api.Assertions.fail; 20 | 21 | import org.junit.jupiter.api.Test; 22 | 23 | /** 24 | * Test CurrencyUnit. 25 | */ 26 | class TestCurrencyUnitExtension { 27 | 28 | @Test 29 | void test_CurrencyFromMoneyData() { 30 | var curList = CurrencyUnit.registeredCurrencies(); 31 | var found = false; 32 | for (CurrencyUnit currencyUnit : curList) { 33 | if (currencyUnit.getCode().equals("GBP")) { 34 | found = true; 35 | break; 36 | } 37 | } 38 | assertThat(found).isTrue(); 39 | } 40 | 41 | @Test 42 | void test_CurrencyFromMoneyDataExtension() { 43 | var curList = CurrencyUnit.registeredCurrencies(); 44 | var found = false; 45 | for (CurrencyUnit currencyUnit : curList) { 46 | if (currencyUnit.getCode().equals("BTC")) { 47 | found = true; 48 | break; 49 | } 50 | } 51 | assertThat(found).isTrue(); 52 | } 53 | 54 | @Test 55 | void test_LargerDecimalPrecisionCurrencyFromMoneyDataExtension() { 56 | var curList = CurrencyUnit.registeredCurrencies(); 57 | var found = false; 58 | for (CurrencyUnit currencyUnit : curList) { 59 | if (currencyUnit.getCode().equals("ETH")) { 60 | found = true; 61 | assertThat(Money.of(currencyUnit, 1.23456789d).toString()).isEqualTo("ETH 1.234567890000000000000000000000"); 62 | break; 63 | } 64 | } 65 | assertThat(found).isTrue(); 66 | } 67 | 68 | @Test 69 | void test_InvalidLargerDecimalPrecisionCurrencyFromMoneyDataExtension() { 70 | for (CurrencyUnit currencyUnit : CurrencyUnit.registeredCurrencies()) { 71 | if (currencyUnit.getCode().equals("XXL")) { 72 | fail("Currency XXL should not exist"); 73 | } 74 | } 75 | } 76 | 77 | @Test 78 | void test_CurrencyMissing() { 79 | for (CurrencyUnit currencyUnit : CurrencyUnit.registeredCurrencies()) { 80 | if (currencyUnit.getCode().equals("NMC")) { 81 | fail("Currency NMC should not exist"); 82 | } 83 | } 84 | } 85 | 86 | @Test 87 | void test_CurrencyEURChanged() { 88 | var currency = CurrencyUnit.ofCountry("HU"); 89 | assertThat(currency).isEqualTo(CurrencyUnit.EUR); 90 | assertThat(CurrencyUnit.EUR.getCountryCodes()).contains("HU"); 91 | assertThat(CurrencyUnit.of("HUF").getCountryCodes()).isEmpty(); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/TestIllegalCurrencyException.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 static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import org.junit.jupiter.api.Test; 21 | 22 | /** 23 | * Test IllegalCurrencyException. 24 | */ 25 | class TestIllegalCurrencyException { 26 | 27 | //----------------------------------------------------------------------- 28 | // new (String) 29 | //----------------------------------------------------------------------- 30 | @Test 31 | void test_String() { 32 | var test = new IllegalCurrencyException("PROBLEM"); 33 | assertThat(test.getMessage()).isEqualTo("PROBLEM"); 34 | assertThat(test.getCause()).isNull(); 35 | } 36 | 37 | @Test 38 | void test_String_nullString() { 39 | var test = new IllegalCurrencyException(null); 40 | assertThat(test.getMessage()).isNull(); 41 | assertThat(test.getCause()).isNull(); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/TestModulepath.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 static java.util.stream.Collectors.joining; 19 | 20 | import java.util.ArrayList; 21 | 22 | import org.junit.jupiter.api.Test; 23 | 24 | /** 25 | * Test that the classpath/modulepath is correctly set. 26 | */ 27 | class TestModulepath { 28 | 29 | @Test 30 | void dumpPaths() { 31 | var classpath = System.getProperty("java.class.path", ""); 32 | var modulepath = System.getProperty("jdk.module.path", ""); 33 | System.out.println("Classpath: " + describePath(classpath)); 34 | System.out.println("Modulepath: " + describePath(modulepath)); 35 | } 36 | 37 | private static String describePath(String path) { 38 | if (path.isEmpty()) { 39 | return ""; 40 | } 41 | var list = new ArrayList(); 42 | if (path.contains("target/classes") || path.contains("target\\classes")) { 43 | list.add("target/classes"); 44 | } 45 | if (path.contains("target/test-classes") || path.contains("target\\test-classes")) { 46 | list.add("target/test-classes"); 47 | } 48 | if (path.contains("joda-convert")) { 49 | list.add("joda-convert"); 50 | } 51 | if (path.contains("junit-jupiter")) { 52 | list.add("junit-jupiter"); 53 | } 54 | return list.isEmpty() ? path : list.stream().collect(joining(" ")); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/TestMoneyUtils_BigMoney.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 static org.assertj.core.api.Assertions.assertThat; 19 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 20 | 21 | import java.lang.reflect.Modifier; 22 | 23 | import org.junit.jupiter.api.Test; 24 | 25 | /** 26 | * Test MoneyUtils. 27 | */ 28 | class TestMoneyUtils_BigMoney { 29 | 30 | private static final BigMoney GBP_0 = BigMoney.parse("GBP 0"); 31 | private static final BigMoney GBP_20 = BigMoney.parse("GBP 20"); 32 | private static final BigMoney GBP_30 = BigMoney.parse("GBP 30"); 33 | private static final BigMoney GBP_50 = BigMoney.parse("GBP 50"); 34 | private static final BigMoney GBP_M10 = BigMoney.parse("GBP -10"); 35 | private static final BigMoney GBP_M30 = BigMoney.parse("GBP -30"); 36 | private static final BigMoney EUR_30 = BigMoney.parse("EUR 30"); 37 | 38 | //----------------------------------------------------------------------- 39 | // constructor 40 | //----------------------------------------------------------------------- 41 | @Test 42 | void test_constructor() throws Exception { 43 | var con = MoneyUtils.class.getDeclaredConstructor(); 44 | assertThat(Modifier.isPrivate(con.getModifiers())).isTrue(); 45 | con.setAccessible(true); 46 | con.newInstance(); 47 | } 48 | 49 | //----------------------------------------------------------------------- 50 | // isZero(BigMoney) 51 | //----------------------------------------------------------------------- 52 | @Test 53 | void test_isZero() { 54 | assertThat(MoneyUtils.isZero(null)).isTrue(); 55 | assertThat(MoneyUtils.isZero(GBP_0)).isTrue(); 56 | assertThat(MoneyUtils.isZero(GBP_30)).isFalse(); 57 | assertThat(MoneyUtils.isZero(GBP_M30)).isFalse(); 58 | } 59 | 60 | //----------------------------------------------------------------------- 61 | // isPositive(BigMoney) 62 | //----------------------------------------------------------------------- 63 | @Test 64 | void test_isPositive() { 65 | assertThat(MoneyUtils.isPositive(null)).isFalse(); 66 | assertThat(MoneyUtils.isPositive(GBP_0)).isFalse(); 67 | assertThat(MoneyUtils.isPositive(GBP_30)).isTrue(); 68 | assertThat(MoneyUtils.isPositive(GBP_M30)).isFalse(); 69 | } 70 | 71 | //----------------------------------------------------------------------- 72 | // isPositiveOrZero(BigMoney) 73 | //----------------------------------------------------------------------- 74 | @Test 75 | void test_isPositiveOrZero() { 76 | assertThat(MoneyUtils.isPositiveOrZero(null)).isTrue(); 77 | assertThat(MoneyUtils.isPositiveOrZero(GBP_0)).isTrue(); 78 | assertThat(MoneyUtils.isPositiveOrZero(GBP_30)).isTrue(); 79 | assertThat(MoneyUtils.isPositiveOrZero(GBP_M30)).isFalse(); 80 | } 81 | 82 | //----------------------------------------------------------------------- 83 | // isNegative(BigMoney) 84 | //----------------------------------------------------------------------- 85 | @Test 86 | void test_isNegative() { 87 | assertThat(MoneyUtils.isNegative(null)).isFalse(); 88 | assertThat(MoneyUtils.isNegative(GBP_0)).isFalse(); 89 | assertThat(MoneyUtils.isNegative(GBP_30)).isFalse(); 90 | assertThat(MoneyUtils.isNegative(GBP_M30)).isTrue(); 91 | } 92 | 93 | //----------------------------------------------------------------------- 94 | // isNegativeOrZero(BigMoney) 95 | //----------------------------------------------------------------------- 96 | @Test 97 | void test_isNegativeOrZero() { 98 | assertThat(MoneyUtils.isNegativeOrZero(null)).isTrue(); 99 | assertThat(MoneyUtils.isNegativeOrZero(GBP_0)).isTrue(); 100 | assertThat(MoneyUtils.isNegativeOrZero(GBP_30)).isFalse(); 101 | assertThat(MoneyUtils.isNegativeOrZero(GBP_M30)).isTrue(); 102 | } 103 | 104 | //----------------------------------------------------------------------- 105 | // max(Money,Money) 106 | //----------------------------------------------------------------------- 107 | @Test 108 | void test_max1() { 109 | assertThat(MoneyUtils.max(GBP_20, GBP_30)).isSameAs(GBP_30); 110 | } 111 | 112 | @Test 113 | void test_max2() { 114 | assertThat(MoneyUtils.max(GBP_30, GBP_20)).isSameAs(GBP_30); 115 | } 116 | 117 | @Test 118 | void test_max_differentCurrencies() { 119 | assertThatExceptionOfType(CurrencyMismatchException.class) 120 | .isThrownBy(() -> MoneyUtils.max(GBP_20, EUR_30)); 121 | } 122 | 123 | @Test 124 | void test_max_null1() { 125 | assertThat(MoneyUtils.max((BigMoney) null, GBP_30)).isSameAs(GBP_30); 126 | } 127 | 128 | @Test 129 | void test_max_null2() { 130 | assertThat(MoneyUtils.max(GBP_20, (BigMoney) null)).isSameAs(GBP_20); 131 | } 132 | 133 | @Test 134 | void test_max_nullBoth() { 135 | assertThat(MoneyUtils.max((BigMoney) null, (BigMoney) null)).isNull(); 136 | } 137 | 138 | //----------------------------------------------------------------------- 139 | // min(Money,Money) 140 | //----------------------------------------------------------------------- 141 | @Test 142 | void test_min1() { 143 | assertThat(MoneyUtils.min(GBP_20, GBP_30)).isSameAs(GBP_20); 144 | } 145 | 146 | @Test 147 | void test_min2() { 148 | assertThat(MoneyUtils.min(GBP_30, GBP_20)).isSameAs(GBP_20); 149 | } 150 | 151 | @Test 152 | void test_min_differentCurrencies() { 153 | assertThatExceptionOfType(CurrencyMismatchException.class) 154 | .isThrownBy(() -> MoneyUtils.min(GBP_20, EUR_30)); 155 | } 156 | 157 | @Test 158 | void test_min_null1() { 159 | assertThat(MoneyUtils.min((BigMoney) null, GBP_30)).isSameAs(GBP_30); 160 | } 161 | 162 | @Test 163 | void test_min_null2() { 164 | assertThat(MoneyUtils.min(GBP_20, (BigMoney) null)).isSameAs(GBP_20); 165 | } 166 | 167 | @Test 168 | void test_min_nullBoth() { 169 | assertThat(MoneyUtils.min((BigMoney) null, (BigMoney) null)).isNull(); 170 | } 171 | 172 | //----------------------------------------------------------------------- 173 | // add(Money,Money) 174 | //----------------------------------------------------------------------- 175 | @Test 176 | void test_add() { 177 | assertThat(MoneyUtils.add(GBP_20, GBP_30)).isEqualTo(GBP_50); 178 | } 179 | 180 | @Test 181 | void test_add_differentCurrencies() { 182 | assertThatExceptionOfType(CurrencyMismatchException.class) 183 | .isThrownBy(() -> MoneyUtils.add(GBP_20, EUR_30)); 184 | } 185 | 186 | @Test 187 | void test_add_null1() { 188 | assertThat(MoneyUtils.add((BigMoney) null, GBP_30)).isSameAs(GBP_30); 189 | } 190 | 191 | @Test 192 | void test_add_null2() { 193 | assertThat(MoneyUtils.add(GBP_20, (BigMoney) null)).isSameAs(GBP_20); 194 | } 195 | 196 | @Test 197 | void test_add_nullBoth() { 198 | assertThat(MoneyUtils.add((BigMoney) null, (BigMoney) null)).isNull(); 199 | } 200 | 201 | //----------------------------------------------------------------------- 202 | // subtract(Money,Money) 203 | //----------------------------------------------------------------------- 204 | @Test 205 | void test_subtract() { 206 | assertThat(MoneyUtils.subtract(GBP_20, GBP_30)).isEqualTo(GBP_M10); 207 | } 208 | 209 | @Test 210 | void test_subtract_differentCurrencies() { 211 | assertThatExceptionOfType(CurrencyMismatchException.class) 212 | .isThrownBy(() -> MoneyUtils.subtract(GBP_20, EUR_30)); 213 | } 214 | 215 | @Test 216 | void test_subtract_null1() { 217 | assertThat(MoneyUtils.subtract((BigMoney) null, GBP_30)).isEqualTo(GBP_M30); 218 | } 219 | 220 | @Test 221 | void test_subtract_null2() { 222 | assertThat(MoneyUtils.subtract(GBP_20, (BigMoney) null)).isSameAs(GBP_20); 223 | } 224 | 225 | @Test 226 | void test_subtract_nullBoth() { 227 | assertThat(MoneyUtils.subtract((BigMoney) null, (BigMoney) null)).isNull(); 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/TestMoneyUtils_Money.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 static org.assertj.core.api.Assertions.assertThat; 19 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 20 | 21 | import org.junit.jupiter.api.Test; 22 | 23 | /** 24 | * Test MoneyUtils. 25 | */ 26 | class TestMoneyUtils_Money { 27 | 28 | private static final Money GBP_0 = Money.parse("GBP 0"); 29 | private static final Money GBP_20 = Money.parse("GBP 20"); 30 | private static final Money GBP_30 = Money.parse("GBP 30"); 31 | private static final Money GBP_50 = Money.parse("GBP 50"); 32 | private static final Money GBP_M10 = Money.parse("GBP -10"); 33 | private static final Money GBP_M30 = Money.parse("GBP -30"); 34 | private static final Money EUR_30 = Money.parse("EUR 30"); 35 | 36 | //----------------------------------------------------------------------- 37 | // checkNotNull(Object,String) 38 | //----------------------------------------------------------------------- 39 | @Test 40 | void test_checkNotNull_notNull() { 41 | MoneyUtils.checkNotNull(new Object(), ""); 42 | } 43 | 44 | @Test 45 | void test_checkNotNull_null() { 46 | assertThatExceptionOfType(NullPointerException.class) 47 | .isThrownBy(() -> MoneyUtils.checkNotNull(null, "Hello")) 48 | .withMessage("Hello"); 49 | } 50 | 51 | //----------------------------------------------------------------------- 52 | // isZero(Money) 53 | //----------------------------------------------------------------------- 54 | @Test 55 | void test_isZero() { 56 | assertThat(MoneyUtils.isZero(null)).isTrue(); 57 | assertThat(MoneyUtils.isZero(GBP_0)).isTrue(); 58 | assertThat(MoneyUtils.isZero(GBP_30)).isFalse(); 59 | assertThat(MoneyUtils.isZero(GBP_M30)).isFalse(); 60 | } 61 | 62 | //----------------------------------------------------------------------- 63 | // isPositive(Money) 64 | //----------------------------------------------------------------------- 65 | @Test 66 | void test_isPositive() { 67 | assertThat(MoneyUtils.isPositive(null)).isFalse(); 68 | assertThat(MoneyUtils.isPositive(GBP_0)).isFalse(); 69 | assertThat(MoneyUtils.isPositive(GBP_30)).isTrue(); 70 | assertThat(MoneyUtils.isPositive(GBP_M30)).isFalse(); 71 | } 72 | 73 | //----------------------------------------------------------------------- 74 | // isPositiveOrZero(Money) 75 | //----------------------------------------------------------------------- 76 | @Test 77 | void test_isPositiveOrZero() { 78 | assertThat(MoneyUtils.isPositiveOrZero(null)).isTrue(); 79 | assertThat(MoneyUtils.isPositiveOrZero(GBP_0)).isTrue(); 80 | assertThat(MoneyUtils.isPositiveOrZero(GBP_30)).isTrue(); 81 | assertThat(MoneyUtils.isPositiveOrZero(GBP_M30)).isFalse(); 82 | } 83 | 84 | //----------------------------------------------------------------------- 85 | // isNegative(Money) 86 | //----------------------------------------------------------------------- 87 | @Test 88 | void test_isNegative() { 89 | assertThat(MoneyUtils.isNegative(null)).isFalse(); 90 | assertThat(MoneyUtils.isNegative(GBP_0)).isFalse(); 91 | assertThat(MoneyUtils.isNegative(GBP_30)).isFalse(); 92 | assertThat(MoneyUtils.isNegative(GBP_M30)).isTrue(); 93 | } 94 | 95 | //----------------------------------------------------------------------- 96 | // isNegativeOrZero(Money) 97 | //----------------------------------------------------------------------- 98 | @Test 99 | void test_isNegativeOrZero() { 100 | assertThat(MoneyUtils.isNegativeOrZero(null)).isTrue(); 101 | assertThat(MoneyUtils.isNegativeOrZero(GBP_0)).isTrue(); 102 | assertThat(MoneyUtils.isNegativeOrZero(GBP_30)).isFalse(); 103 | assertThat(MoneyUtils.isNegativeOrZero(GBP_M30)).isTrue(); 104 | } 105 | 106 | //----------------------------------------------------------------------- 107 | // max(Money,Money) 108 | //----------------------------------------------------------------------- 109 | @Test 110 | void test_max1() { 111 | assertThat(MoneyUtils.max(GBP_20, GBP_30)).isSameAs(GBP_30); 112 | } 113 | 114 | @Test 115 | void test_max2() { 116 | assertThat(MoneyUtils.max(GBP_30, GBP_20)).isSameAs(GBP_30); 117 | } 118 | 119 | @Test 120 | void test_max_differentCurrencies() { 121 | assertThatExceptionOfType(CurrencyMismatchException.class) 122 | .isThrownBy(() -> MoneyUtils.max(GBP_20, EUR_30)); 123 | } 124 | 125 | @Test 126 | void test_max_null1() { 127 | assertThat(MoneyUtils.max((Money) null, GBP_30)).isSameAs(GBP_30); 128 | } 129 | 130 | @Test 131 | void test_max_null2() { 132 | assertThat(MoneyUtils.max(GBP_20, (Money) null)).isSameAs(GBP_20); 133 | } 134 | 135 | @Test 136 | void test_max_nullBoth() { 137 | assertThat(MoneyUtils.max((Money) null, (Money) null)).isNull(); 138 | } 139 | 140 | //----------------------------------------------------------------------- 141 | // min(Money,Money) 142 | //----------------------------------------------------------------------- 143 | @Test 144 | void test_min1() { 145 | assertThat(MoneyUtils.min(GBP_20, GBP_30)).isSameAs(GBP_20); 146 | } 147 | 148 | @Test 149 | void test_min2() { 150 | assertThat(MoneyUtils.min(GBP_30, GBP_20)).isSameAs(GBP_20); 151 | } 152 | 153 | @Test 154 | void test_min_differentCurrencies() { 155 | assertThatExceptionOfType(CurrencyMismatchException.class) 156 | .isThrownBy(() -> MoneyUtils.min(GBP_20, EUR_30)); 157 | } 158 | 159 | @Test 160 | void test_min_null1() { 161 | assertThat(MoneyUtils.min((Money) null, GBP_30)).isSameAs(GBP_30); 162 | } 163 | 164 | @Test 165 | void test_min_null2() { 166 | assertThat(MoneyUtils.min(GBP_20, (Money) null)).isSameAs(GBP_20); 167 | } 168 | 169 | @Test 170 | void test_min_nullBoth() { 171 | assertThat(MoneyUtils.min((Money) null, (Money) null)).isNull(); 172 | } 173 | 174 | //----------------------------------------------------------------------- 175 | // add(Money,Money) 176 | //----------------------------------------------------------------------- 177 | @Test 178 | void test_add() { 179 | assertThat(MoneyUtils.add(GBP_20, GBP_30)).isEqualTo(GBP_50); 180 | } 181 | 182 | @Test 183 | void test_add_differentCurrencies() { 184 | assertThatExceptionOfType(CurrencyMismatchException.class) 185 | .isThrownBy(() -> MoneyUtils.add(GBP_20, EUR_30)); 186 | } 187 | 188 | @Test 189 | void test_add_null1() { 190 | assertThat(MoneyUtils.add((Money) null, GBP_30)).isSameAs(GBP_30); 191 | } 192 | 193 | @Test 194 | void test_add_null2() { 195 | assertThat(MoneyUtils.add(GBP_20, (Money) null)).isSameAs(GBP_20); 196 | } 197 | 198 | @Test 199 | void test_add_nullBoth() { 200 | assertThat(MoneyUtils.add((Money) null, (Money) null)).isNull(); 201 | } 202 | 203 | //----------------------------------------------------------------------- 204 | // subtract(Money,Money) 205 | //----------------------------------------------------------------------- 206 | @Test 207 | void test_subtract() { 208 | assertThat(MoneyUtils.subtract(GBP_20, GBP_30)).isEqualTo(GBP_M10); 209 | } 210 | 211 | @Test 212 | void test_subtract_differentCurrencies() { 213 | assertThatExceptionOfType(CurrencyMismatchException.class) 214 | .isThrownBy(() -> MoneyUtils.subtract(GBP_20, EUR_30)); 215 | } 216 | 217 | @Test 218 | void test_subtract_null1() { 219 | assertThat(MoneyUtils.subtract((Money) null, GBP_30)).isEqualTo(GBP_M30); 220 | } 221 | 222 | @Test 223 | void test_subtract_null2() { 224 | assertThat(MoneyUtils.subtract(GBP_20, (Money) null)).isSameAs(GBP_20); 225 | } 226 | 227 | @Test 228 | void test_subtract_nullBoth() { 229 | assertThat(MoneyUtils.subtract((Money) null, (Money) null)).isNull(); 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/TestStringConvert.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 static org.assertj.core.api.Assertions.assertThat; 19 | 20 | import org.joda.convert.StringConvert; 21 | import org.junit.jupiter.api.Test; 22 | 23 | /** 24 | * Test string conversion. 25 | */ 26 | class TestStringConvert { 27 | 28 | @Test 29 | void test_BigMoney() { 30 | var test = BigMoney.of(CurrencyUnit.CHF, 1234.5678d); 31 | var str = StringConvert.INSTANCE.convertToString(test); 32 | assertThat(str).isEqualTo("CHF 1234.5678"); 33 | assertThat(StringConvert.INSTANCE.convertFromString(BigMoney.class, str)).isEqualTo(test); 34 | } 35 | 36 | @Test 37 | void test_Money() { 38 | var test = Money.of(CurrencyUnit.CHF, 1234.56d); 39 | var str = StringConvert.INSTANCE.convertToString(test); 40 | assertThat(str).isEqualTo("CHF 1234.56"); 41 | assertThat(StringConvert.INSTANCE.convertFromString(Money.class, str)).isEqualTo(test); 42 | } 43 | 44 | @Test 45 | void test_CurrencyUnit() { 46 | var test = CurrencyUnit.CHF; 47 | var str = StringConvert.INSTANCE.convertToString(test); 48 | assertThat(str).isEqualTo("CHF"); 49 | assertThat(StringConvert.INSTANCE.convertFromString(CurrencyUnit.class, str)).isEqualTo(test); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/format/TestMoneyFormatterException.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 static org.assertj.core.api.Assertions.assertThatNoException; 19 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 20 | 21 | import java.io.IOException; 22 | 23 | import org.junit.jupiter.api.Test; 24 | 25 | /** 26 | * Test MoneyFormatterException. 27 | */ 28 | class TestMoneyFormatterException { 29 | 30 | @Test 31 | void test_MoneyFormatException_IOException_notRethrown() { 32 | var test = new MoneyFormatException("Error", new IOException("Inner")); 33 | assertThatExceptionOfType(IOException.class) 34 | .isThrownBy(() -> test.rethrowIOException()); 35 | } 36 | 37 | @Test 38 | void test_MoneyFormatException_nonIOException_notRethrown() throws IOException { 39 | var test = new MoneyFormatException("Error", new IllegalStateException("Inner")); 40 | assertThatNoException() 41 | .isThrownBy(() -> test.rethrowIOException()); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/joda/money/format/TestMoneyParseContext.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 static org.assertj.core.api.Assertions.assertThat; 19 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; 20 | 21 | import java.math.BigDecimal; 22 | import java.text.ParsePosition; 23 | import java.util.Locale; 24 | 25 | import org.joda.money.CurrencyUnit; 26 | import org.junit.jupiter.api.Test; 27 | 28 | /** 29 | * Test MoneyParseContext. 30 | */ 31 | class TestMoneyParseContext { 32 | 33 | @Test 34 | void test_initialState() { 35 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 36 | assertThat(test.getAmount()).isNull(); 37 | assertThat(test.getCurrency()).isNull(); 38 | assertThat(test.getIndex()).isEqualTo(0); 39 | assertThat(test.getErrorIndex()).isEqualTo(-1); 40 | assertThat(test.getText()).hasToString("GBP 123"); 41 | assertThat(test.getTextLength()).isEqualTo(7); 42 | assertThat(test.isError()).isFalse(); 43 | assertThat(test.isFullyParsed()).isFalse(); 44 | assertThat(test.isComplete()).isFalse(); 45 | var pp = new ParsePosition(0); 46 | pp.setErrorIndex(-1); 47 | assertThat(test.toParsePosition()).isEqualTo(pp); 48 | } 49 | 50 | @Test 51 | void test_setIndex() { 52 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 53 | assertThat(test.getIndex()).isEqualTo(0); 54 | test.setIndex(2); 55 | assertThat(test.getIndex()).isEqualTo(2); 56 | } 57 | 58 | @Test 59 | void test_setErrorIndex() { 60 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 61 | assertThat(test.getErrorIndex()).isEqualTo(-1); 62 | test.setErrorIndex(3); 63 | assertThat(test.getErrorIndex()).isEqualTo(3); 64 | } 65 | 66 | @Test 67 | void test_setError() { 68 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 69 | assertThat(test.getIndex()).isEqualTo(0); 70 | assertThat(test.getErrorIndex()).isEqualTo(-1); 71 | test.setError(); 72 | assertThat(test.getIndex()).isEqualTo(0); 73 | assertThat(test.getErrorIndex()).isEqualTo(0); 74 | } 75 | 76 | @Test 77 | void test_setError_withIndex() { 78 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 79 | assertThat(test.getIndex()).isEqualTo(0); 80 | assertThat(test.getErrorIndex()).isEqualTo(-1); 81 | test.setIndex(2); 82 | test.setError(); 83 | assertThat(test.getIndex()).isEqualTo(2); 84 | assertThat(test.getErrorIndex()).isEqualTo(2); 85 | } 86 | 87 | //----------------------------------------------------------------------- 88 | @Test 89 | void test_isComplete_noCurrency() { 90 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 91 | test.setAmount(BigDecimal.TEN); 92 | assertThat(test.isComplete()).isFalse(); 93 | } 94 | 95 | @Test 96 | void test_isComplete_noAmount() { 97 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 98 | test.setCurrency(CurrencyUnit.GBP); 99 | assertThat(test.isComplete()).isFalse(); 100 | } 101 | 102 | @Test 103 | void test_toBigMoney_noCurrency() { 104 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 105 | test.setAmount(BigDecimal.TEN); 106 | assertThatExceptionOfType(MoneyFormatException.class) 107 | .isThrownBy(() -> test.toBigMoney()); 108 | } 109 | 110 | @Test 111 | void test_toBigMoney_noAmount() { 112 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 113 | test.setCurrency(CurrencyUnit.GBP); 114 | assertThatExceptionOfType(MoneyFormatException.class) 115 | .isThrownBy(() -> test.toBigMoney()); 116 | } 117 | 118 | //----------------------------------------------------------------------- 119 | @Test 120 | void test_getTextSubstring_ok() { 121 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 122 | assertThat(test.getTextSubstring(0, 2)).isEqualTo("GB"); 123 | assertThat(test.getTextSubstring(5, 7)).isEqualTo("23"); 124 | } 125 | 126 | @Test 127 | void test_getTextSubstring_beforeStart() { 128 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 129 | assertThatExceptionOfType(IndexOutOfBoundsException.class) 130 | .isThrownBy(() -> test.getTextSubstring(-1, 2)); 131 | } 132 | 133 | @Test 134 | void test_getTextSubstring_afterEnd() { 135 | var test = new MoneyParseContext(Locale.FRANCE, "GBP 123", 0); 136 | assertThatExceptionOfType(IndexOutOfBoundsException.class) 137 | .isThrownBy(() -> test.getTextSubstring(0, 8)); 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/org/joda/money/CountryDataExtension.csv: -------------------------------------------------------------------------------- 1 | #Country,Currency 2 | HU,EUR 3 | -------------------------------------------------------------------------------- /src/test/resources/META-INF/org/joda/money/CurrencyDataExtension.csv: -------------------------------------------------------------------------------- 1 | #Code,Numeric,DecPlaces 2 | BTC,-1,-1 3 | ETH,-1,30 4 | XXL,-1,31 5 | EUR,978,2 6 | HUF,348,2 7 | --------------------------------------------------------------------------------