├── .envrc ├── .git-blame-ignore-revs ├── .github ├── release-drafter.yml └── workflows │ ├── ci.yml │ ├── clean.yml │ ├── fmt.yml │ └── release-drafter.yml ├── .gitignore ├── .jvmopts ├── .mergify.yml ├── .scalafmt.conf ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── build.sbt ├── core ├── js │ └── src │ │ └── main │ │ ├── scala-2 │ │ └── java │ │ │ └── text │ │ │ └── Normalizer.scala │ │ ├── scala-3 │ │ └── java │ │ │ └── text │ │ │ └── Normalizer.scala │ │ └── scala │ │ └── locales │ │ └── DefaultLocale.scala ├── jvm │ └── src │ │ └── main │ │ └── scala │ │ └── locales │ │ └── DefaultLocale.scala ├── native │ └── src │ │ └── main │ │ ├── scala-2 │ │ └── java │ │ │ └── text │ │ │ └── Normalizer.scala │ │ ├── scala-3 │ │ └── java │ │ │ └── text │ │ │ └── Normalizer.scala │ │ └── scala │ │ └── locales │ │ ├── DefaultLocale.scala │ │ └── NormalizerImpl.scala └── shared │ └── src │ └── main │ ├── scala-2.11 │ └── scala │ │ ├── collection │ │ └── compat │ │ │ ├── CompatImpl.scala │ │ │ ├── PackageShared.scala │ │ │ └── package.scala │ │ └── jdk │ │ └── CollectionConverters.scala │ ├── scala-2.12 │ └── scala │ │ ├── collection │ │ └── compat │ │ │ ├── CompatImpl.scala │ │ │ ├── PackageShared.scala │ │ │ └── package.scala │ │ └── jdk │ │ └── CollectionConverters.scala │ ├── scala-2.13 │ └── scala │ │ ├── collection │ │ └── compat │ │ │ ├── immutable │ │ │ └── package.scala │ │ │ └── package.scala │ │ └── util │ │ └── control │ │ └── compat │ │ └── package.scala │ ├── scala-2 │ └── java │ │ └── util │ │ └── Locale.scala │ ├── scala-3 │ └── java │ │ └── util │ │ └── Locale.scala │ └── scala │ ├── java │ ├── text │ │ ├── AttributedCharacterIterator.scala │ │ ├── CharacterIterator.scala │ │ ├── DateFormat.scala │ │ ├── DateFormatSymbols.scala │ │ ├── DecimalFormat.scala │ │ ├── DecimalFormatSymbols.scala │ │ ├── FieldPosition.scala │ │ ├── Format.scala │ │ ├── NumberFormat.scala │ │ ├── ParseException.scala │ │ ├── ParsePosition.scala │ │ └── SimpleDateFormat.scala │ └── util │ │ └── Currency.scala │ └── locales │ ├── LocalesDb.scala │ └── cldr │ └── fallback │ ├── data │ ├── data.scala │ └── numericsystems.scala │ └── ldmlprovider.scala ├── demo └── shared │ └── src │ └── main │ └── scala │ └── demo │ └── DemoApp.scala ├── flake.lock ├── flake.nix ├── localesFullDb └── shared │ └── src │ └── main │ └── scala │ └── package.scala ├── macroutils └── src │ └── main │ ├── scala-2.11 │ ├── JVMDate.scala │ └── TreeHelper.scala │ ├── scala-2.12 │ ├── JVMDate.scala │ └── TreeHelper.scala │ ├── scala-2.13 │ ├── JVMDate.scala │ └── TreeHelper.scala │ └── scala-3 │ └── JVMDate.scala ├── project ├── build.properties └── plugins.sbt └── tests ├── js-jvm └── src │ └── test │ └── scala │ └── testsuite │ └── javalib │ └── text │ └── SimpleDateFormatTest.scala ├── js-native └── src │ └── test │ └── scala │ └── testsuite │ └── locales │ └── BCP47Test.scala └── shared └── src └── test └── scala └── testsuite ├── javalib ├── text │ ├── AttributedCharacterIteratorTest.scala │ ├── CharacterIteratorTest.scala │ ├── DateFormatSymbolsTest.scala │ ├── DateFormatTest.scala │ ├── DecimalFormatSymbolsTest.scala │ ├── DecimalFormatTest.scala │ ├── FieldPositionTest.scala │ ├── NormalizerTest.scala │ ├── NumberFormatTest.scala │ └── ParsePositionTest.scala └── util │ ├── CurrencyTest.scala │ ├── LocaleBuilderTest.scala │ ├── LocaleCategoryTest.scala │ └── LocaleTest.scala └── utils └── Platform.scala /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | layout node 3 | eval "$shellHook" 4 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Scala Steward: Reformat with scalafmt 3.5.8 2 | 162a6489db275c39b21d257d9d95ec0e3f373c34 3 | 4 | # Scala Steward: Reformat with scalafmt 3.5.9 5 | 0ec3525b6d0ba122777640a9c604c92b1a32593a 6 | 7 | # Scala Steward: Reformat with scalafmt 3.7.2 8 | 25cfd2f06879985749802f2b54047f1b29e240b7 9 | 10 | # Scala Steward: Reformat with scalafmt 3.7.5 11 | 227768882464e0846c22da2031211c579dbcca10 12 | 13 | # Scala Steward: Reformat with scalafmt 3.8.2 14 | 32114d9e9e6fc91aab23e6042e46157c4a5253af 15 | 16 | # Scala Steward: Reformat with scalafmt 3.8.4 17 | 1f703b5dae8779c51d97236fcbbde73767135a5b 18 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$NEXT_PATCH_VERSION' 2 | tag-template: 'v$NEXT_PATCH_VERSION' 3 | template: | 4 | # What's Changed 5 | $CHANGES 6 | categories: 7 | - title: 'New' 8 | label: 'type: feature' 9 | - title: 'Bug Fixes' 10 | label: 'type: bug' 11 | - title: 'Maintenance' 12 | label: 'type: maintenance' 13 | - title: 'Documentation' 14 | label: 'type: docs' 15 | - title: 'Dependency Updates' 16 | label: 'type: dependencies' 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Continuous Integration 9 | 10 | on: 11 | pull_request: 12 | branches: ['**'] 13 | push: 14 | branches: ['**'] 15 | tags: [v*] 16 | 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | jobs: 21 | build: 22 | name: Build and Test 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | os: [ubuntu-latest] 27 | scala: [2.12.20, 2.13.16, 3.3.5] 28 | java: [temurin@8, temurin@17] 29 | runs-on: ${{ matrix.os }} 30 | steps: 31 | - name: Checkout current branch (full) 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 35 | 36 | - name: Setup Java (temurin@8) 37 | if: matrix.java == 'temurin@8' 38 | uses: actions/setup-java@v4 39 | with: 40 | distribution: temurin 41 | java-version: 8 42 | cache: sbt 43 | 44 | - name: Setup Java (temurin@17) 45 | if: matrix.java == 'temurin@17' 46 | uses: actions/setup-java@v4 47 | with: 48 | distribution: temurin 49 | java-version: 17 50 | cache: sbt 51 | 52 | - name: Install libutf8proc 53 | run: sudo apt-get install libutf8proc-dev 54 | 55 | - name: Check that workflows are up to date 56 | run: sbt '++ ${{ matrix.scala }}' githubWorkflowCheck 57 | 58 | - name: Build project 59 | run: sbt '++ ${{ matrix.scala }}' test 60 | 61 | - name: Compress target directories 62 | run: tar cf targets.tar target localesFullCurrenciesDb/js/target core/native/target localesMinimalEnUSDb/native/target macroutils/target core/js/target localesFullCurrenciesDb/native/target core/jvm/target tests/js/target localesFullDb/.js/target localesFullDb/.native/target localesMinimalEnUSDb/js/target tests/jvm/target demo/native/target localesMinimalEnDb/native/target localesMinimalEnDb/js/target tests/native/target demo/js/target project/target 63 | 64 | - name: Upload target directories 65 | uses: actions/upload-artifact@v4 66 | with: 67 | name: target-${{ matrix.os }}-${{ matrix.scala }}-${{ matrix.java }} 68 | path: targets.tar 69 | 70 | publish: 71 | name: Publish Artifacts 72 | needs: [build] 73 | if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) 74 | strategy: 75 | matrix: 76 | os: [ubuntu-latest] 77 | scala: [2.13.16] 78 | java: [temurin@8] 79 | runs-on: ${{ matrix.os }} 80 | steps: 81 | - name: Checkout current branch (full) 82 | uses: actions/checkout@v4 83 | with: 84 | fetch-depth: 0 85 | 86 | - name: Setup Java (temurin@8) 87 | if: matrix.java == 'temurin@8' 88 | uses: actions/setup-java@v4 89 | with: 90 | distribution: temurin 91 | java-version: 8 92 | cache: sbt 93 | 94 | - name: Setup Java (temurin@17) 95 | if: matrix.java == 'temurin@17' 96 | uses: actions/setup-java@v4 97 | with: 98 | distribution: temurin 99 | java-version: 17 100 | cache: sbt 101 | 102 | - name: Download target directories (2.12.20) 103 | uses: actions/download-artifact@v4 104 | with: 105 | name: target-${{ matrix.os }}-2.12.20-${{ matrix.java }} 106 | 107 | - name: Inflate target directories (2.12.20) 108 | run: | 109 | tar xf targets.tar 110 | rm targets.tar 111 | 112 | - name: Download target directories (2.13.16) 113 | uses: actions/download-artifact@v4 114 | with: 115 | name: target-${{ matrix.os }}-2.13.16-${{ matrix.java }} 116 | 117 | - name: Inflate target directories (2.13.16) 118 | run: | 119 | tar xf targets.tar 120 | rm targets.tar 121 | 122 | - name: Download target directories (3.3.5) 123 | uses: actions/download-artifact@v4 124 | with: 125 | name: target-${{ matrix.os }}-3.3.5-${{ matrix.java }} 126 | 127 | - name: Inflate target directories (3.3.5) 128 | run: | 129 | tar xf targets.tar 130 | rm targets.tar 131 | 132 | - env: 133 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 134 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 135 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 136 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 137 | run: sbt ci-release 138 | -------------------------------------------------------------------------------- /.github/workflows/clean.yml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by sbt-github-actions using the 2 | # githubWorkflowGenerate task. You should add and commit this file to 3 | # your git repository. It goes without saying that you shouldn't edit 4 | # this file by hand! Instead, if you wish to make changes, you should 5 | # change your sbt build configuration to revise the workflow description 6 | # to meet your needs, then regenerate this file. 7 | 8 | name: Clean 9 | 10 | on: push 11 | 12 | jobs: 13 | delete-artifacts: 14 | name: Delete Artifacts 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | steps: 19 | - name: Delete artifacts 20 | shell: bash {0} 21 | run: | 22 | # Customize those three lines with your repository and credentials: 23 | REPO=${GITHUB_API_URL}/repos/${{ github.repository }} 24 | 25 | # A shortcut to call GitHub API. 26 | ghapi() { curl --silent --location --user _:$GITHUB_TOKEN "$@"; } 27 | 28 | # A temporary file which receives HTTP response headers. 29 | TMPFILE=$(mktemp) 30 | 31 | # An associative array, key: artifact name, value: number of artifacts of that name. 32 | declare -A ARTCOUNT 33 | 34 | # Process all artifacts on this repository, loop on returned "pages". 35 | URL=$REPO/actions/artifacts 36 | while [[ -n "$URL" ]]; do 37 | 38 | # Get current page, get response headers in a temporary file. 39 | JSON=$(ghapi --dump-header $TMPFILE "$URL") 40 | 41 | # Get URL of next page. Will be empty if we are at the last page. 42 | URL=$(grep '^Link:' "$TMPFILE" | tr ',' '\n' | grep 'rel="next"' | head -1 | sed -e 's/.*.*//') 43 | rm -f $TMPFILE 44 | 45 | # Number of artifacts on this page: 46 | COUNT=$(( $(jq <<<$JSON -r '.artifacts | length') )) 47 | 48 | # Loop on all artifacts on this page. 49 | for ((i=0; $i < $COUNT; i++)); do 50 | 51 | # Get name of artifact and count instances of this name. 52 | name=$(jq <<<$JSON -r ".artifacts[$i].name?") 53 | ARTCOUNT[$name]=$(( $(( ${ARTCOUNT[$name]} )) + 1)) 54 | 55 | id=$(jq <<<$JSON -r ".artifacts[$i].id?") 56 | size=$(( $(jq <<<$JSON -r ".artifacts[$i].size_in_bytes?") )) 57 | printf "Deleting '%s' #%d, %'d bytes\n" $name ${ARTCOUNT[$name]} $size 58 | ghapi -X DELETE $REPO/actions/artifacts/$id 59 | done 60 | done 61 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: scalafmt 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: coursier/cache-action@v5 12 | - name: Set up JDK 1.8 13 | uses: olafurpg/setup-scala@v10 14 | with: 15 | java-version: 1.8 16 | - name: Checking your code format 17 | run: sbt scalafmtCheckAll 18 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Drafts your next Release notes as Pull Requests are merged into "master" 14 | - uses: release-drafter/release-drafter@v5 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | core/src/main/resources 2 | core/.js 3 | core/.jvm 4 | core/.native 5 | target/ 6 | .bsp/ 7 | .metals 8 | .bloop 9 | .idea 10 | metals.sbt 11 | node_modules/ 12 | .direnv/ 13 | -------------------------------------------------------------------------------- /.jvmopts: -------------------------------------------------------------------------------- 1 | -XX:+PrintCommandLineFlags 2 | -Xss4M 3 | -Xmx4G 4 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | 2 | pull_request_rules: 3 | - name: automatically merge scala-steward's PRs 4 | conditions: 5 | - author=scala-steward 6 | - body~=labels:.*semver-patch.* 7 | - status-success=build 8 | actions: 9 | merge: 10 | method: merge 11 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.8.6" 2 | style = default 3 | 4 | maxColumn = 100 5 | 6 | // Vertical alignment is pretty, but leads to bigger diffs 7 | align.preset = most 8 | 9 | rewrite.rules = [ 10 | AvoidInfix 11 | RedundantBraces 12 | RedundantParens 13 | AsciiSortImports 14 | PreferCurlyFors 15 | ] 16 | 17 | align.tokens.add = [{code = "=>", owner = "Case"}] 18 | align.tokens.add = [{code = ":", owner = "Term.Param"}, "=", "shouldBe", "<-", "^"] 19 | align.openParenCallSite = true 20 | spaces.inImportCurlyBraces = true 21 | 22 | continuationIndent.defnSite = 2 23 | 24 | rewrite.neverInfix.excludeFilters = [until 25 | to 26 | by 27 | eq 28 | ne 29 | "should.*" 30 | "contain.*" 31 | "must.*" 32 | in 33 | be 34 | taggedAs 35 | thrownBy 36 | synchronized 37 | have 38 | when 39 | size 40 | theSameElementsAs] 41 | 42 | runner.dialect = scala213 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.0 4 | 5 | * Drop support for scala.js 0.6 6 | * Add support for scala 3.0.0-M2 7 | * Multiple library update 8 | 9 | ## 1.0.0 10 | 11 | * Update scala to 2.12.11 12 | * Update scala to 2.13.2 13 | * Update sbt-locales to 1.0.0 14 | * Fix the default locale logic 15 | 16 | ## 0.6.0 17 | 18 | * Support scala 2.13.1, 2.12.10, 2.11.10 19 | * Dropped support for 2.10 20 | * Support Scala.js 0.6.32 and 1.0.1 21 | * Implement Locale#getISO3Country 22 | * Implement Locale#getISO3Language 23 | * Unification of 0.3 and 0.5 branches, it includes currency support 24 | * Does not include the locales db, it must be built with sbt-locales 25 | * Across the board improvements: 26 | * Format with scalafmt 27 | * Uses GH actions 28 | * Uses sbt-tpolecat and sbt-ci-release 29 | 30 | ## 0.3.11-cldr33 31 | 32 | * Scala.js 0.6.25 and 1.0.0-M4 33 | * Scala versions 2.10.7, 2.11.13, 2.12.5 and 2.13.0-M4 34 | * Scala native 0.3.8 35 | 36 | ## 0.3.10-cldr32 37 | 38 | * Scala.js 0.6.22 and 1.0.0-M3 39 | 40 | ## 0.3.9-cldr32 41 | 42 | * Scala.js 0.6.21 and 1.0.0-M2 43 | * Scala versions 2.10.7, 2.11.12, 2.12.4 and 2.13.0-M2 44 | * Updated to cldr32 45 | 46 | ## 0.3.8-cldr31 47 | 48 | * Scala.js 0.6.20 and 1.0.0-M1 49 | * Scala versions 2.11.11, 2.12.3 and 2.13.0-M2 50 | * Support for scala-native 51 | 52 | ## 0.3.7-cldr31 53 | 54 | * Updated to scala.js 0.6.18 55 | * Supports scala.js 1.0.0-M1 56 | 57 | ## 0.3.5-cldr31 58 | 59 | * Updated to scala.js 0.6.17 60 | 61 | ## 0.3.4-cldr31 62 | 63 | * Updated to scala.js 0.6.16 64 | * Updated to scala 2.11.11 65 | * Updated to scala 2.12.2 66 | 67 | ## 0.3.3-cldr31 68 | 69 | * Improved SimpleDateFormat with a partial implementation of format 70 | * Updated to scala.js 0.6.15 71 | * Updated CLDR to version 31 72 | 73 | ## 0.3.2-cldr30 74 | 75 | * Updated to scala.js 0.6.14 and scala 2.12.1 76 | * Use io.github.cquiroz organization 77 | * Fix source map path 78 | 79 | ## 0.3.1-cldr30 80 | 81 | * Updated to scala.js 0.6.13 and scala 2.12.0 82 | 83 | ## 0.3.0-cldr30 84 | 85 | * Updated CLDR to version 30 86 | * Updated to scala.js 0.6.12 and scala 2.12.0-RC1 87 | 88 | ## 0.3.0-cldr29 89 | 90 | Provides minimal implementation of DateFormat for scala-java-time 91 | 92 | * Add basic implementation java.text.NumberFormat 93 | * Add basic implementation java.text.DateFormat 94 | * Add basic implementation java.text.SimpleDateFormat 95 | 96 | ## 0.2.0-cldr29 97 | 98 | * Add class java.text.Format 99 | * Add class java.text.FieldPosition 100 | * Add class java.text.CharacterIterator 101 | * Add class java.text.AttributedCharacterIterator 102 | * Add class java.text.ParsePosition 103 | * Add class java.text.ParseException 104 | 105 | ## 0.1.1-cldr29 106 | 107 | * Cross build and publish to scala 2.10.6, 2.11.8 and 2.12.0-M5 108 | 109 | ## 0.1.0-cldr29 110 | 111 | First release of scala-java-locales: 112 | 113 | * Partial implementation of Locale, Locale.Category and Locale.Builder classes 114 | * Code generation of locales metadata from CLDR code 115 | * Partial implementation of DecimalFormatSymbols with CLDR data 116 | * Partial implementation of DateFormatSymbols with CLDR data 117 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2020 Carlos Quiroz 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | * Neither the name of Carlos Quiroz nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | ------------------------------------------------------------------------------ 30 | Generated code is built from the Unicode CLDR data found at 31 | http://www.unicode.org/cldr/data/. It comes with the following license. 32 | 33 | COPYRIGHT AND PERMISSION NOTICE 34 | 35 | Copyright (c) 1991-2013 Unicode, Inc. All rights reserved. Distributed under 36 | the Terms of Use in http://www.unicode.org/copyright.html. 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy of 39 | the Unicode data files and any associated documentation (the "Data Files") or 40 | Unicode software and any associated documentation (the "Software") to deal in 41 | the Data Files or Software without restriction, including without limitation 42 | the rights to use, copy, modify, merge, publish, distribute, and/or sell copies 43 | of the Data Files or Software, and to permit persons to whom the Data Files or 44 | Software are furnished to do so, provided that (a) the above copyright 45 | notice(s) and this permission notice appear with all copies of the Data Files 46 | or Software, (b) both the above copyright notice(s) and this permission notice 47 | appear in associated documentation, and (c) there is clear notice in each 48 | modified Data File or in the Software as well as in the documentation 49 | associated with the Data File(s) or Software that the data or software has been 50 | modified. 51 | 52 | THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 53 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 54 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD 55 | PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN 56 | THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL 57 | DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 58 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 59 | OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR 60 | SOFTWARE. 61 | 62 | Except as contained in this notice, the name of a copyright holder shall not be 63 | used in advertising or otherwise to promote the sale, use or other dealings in 64 | these Data Files or Software without prior written authorization of the 65 | copyright holder. 66 | 67 | Unicode and the Unicode logo are trademarks of Unicode, Inc. in the United 68 | States and other countries. All third party trademarks referenced herein are 69 | the property of their respective owners. 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scala-java-locales 2 | 3 | ![build](https://github.com/cquiroz/scala-java-locales/workflows/build/badge.svg) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.cquiroz/scala-java-locales_sjs1_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.cquiroz/scala-java-locales_sjs1_2.13) 5 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.0.0.svg)](https://www.scala-js.org/) 6 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-0.6.29.svg)](https://www.scala-js.org/) 7 | 8 | `scala-java-locales` is a clean-room BSD-licensed implementation of the `java.util.Locale` API and related classes as defined on JDK8, mostly for Scala.js usage. It enables the locale API in Scala.js projects and supports usage requiring locales like number and dates formatting. 9 | 10 | ## Usage 11 | 12 | Simply add the following line to your sbt settings: 13 | 14 | ```scala 15 | libraryDependencies += "io.github.cquiroz" %%% "scala-java-locales" % "1.2.0" 16 | ``` 17 | 18 | If you have a `crossProject`, the setting must be used only in the JS part: 19 | 20 | ```scala 21 | lazy val myCross = crossProject. 22 | ... 23 | .jsSettings( 24 | libraryDependencies += "io.github.cquiroz" %%% "scala-java-locales" % "1.2.0" 25 | ) 26 | ``` 27 | 28 | **Requirement**: you must use a host JDK8 to _build_ your project, i.e., to 29 | launch sbt. `scala-java-locales` does not work on earlier JDKs. 30 | 31 | ## Work in Progress / linking errors 32 | 33 | This library is a work in progress and there are some unimplemented methods. If you use any of those on your Scala.js code, you will get linking errors. 34 | 35 | ## Usage 36 | 37 | The API follows the Java API for Locales, any major difference should be considered a bug. 38 | 39 | The JVM includes a large locales database derived from CLDR. That includes things like date 40 | formats, region names, etc. 41 | Having the full db on js is possible but expensive in terms of space and for most applications 42 | only a few locales are needed, thus it is simpler to have a subset of them using some of the 43 | provided locale dbs or even better via [sbt-locales](http://github.com/cquiroz/sbt-locales) 44 | `sbt-locales` lets you build a custom db with the minimal amount you need. There is a slight 45 | size benefit and a larger speed improvement doing so as scala.js has less code to optimize 46 | 47 | For the common cases that you just need date formatting in angling you can just include 48 | 49 | ```scala 50 | libraryDependencies += "io.github.cquiroz" %%% "locales-minimal-en-db" % "1.2.0" 51 | ``` 52 | 53 | ## Default Locale 54 | 55 | Starting on 0.6.0 it is no longer necessary to register locales but only a minimal locale based on English is 56 | provided. You may want to use [sbt-locales](https://github.com/cquiroz/sbt-locales) to generate 57 | a custom locale database. 58 | 59 | For example see: 60 | [gemini-locales](https://github.com/gemini-hlsw/gemini-locales/) 61 | 62 | It is highly recommended to set your default Locale at the start of your application 63 | ``` 64 | Locale.setDefault(Locale.forLanguageTag()) 65 | ``` 66 | The Java API requires a default `Locale` though it doesn't mandate a specific one, instead, the runtime should select it depending on the platform. 67 | 68 | While the Java Locales use the OS default locale, on `Scala.js` platforms like browsers or node.js, it is harder to identify the default locale . `scala-java-locales` will try to guess the locale but if it can't or it is not not the locales db it sets `en (English)` as the default locale. This is a design decision to support the many API calls that require a default locale. It seems that `Scala.js` _de facto_ uses `en` for number formatting. 69 | 70 | ## CLDR 71 | 72 | `java.util.Locale` is a relatively simple class and by itself it doesn't provide too much functionality. The key for its usefulness is on providing data about the locale especially in terms of classes like `java.text.DecimalFormatSymbols`, `java.text.DateFormatSymbols`, etc. The [Unicode CLDR](http://cldr.unicode.org/) project is a large repository of locale data that can be used to build the supporting classes, e.g. to get the `DecimalFormatSymbols` for a given locale. 73 | 74 | Starting on Java 8, [CLDR](https://docs.oracle.com/javase/8/docs/technotes/guides/intl/enhancements.8.html#cldr) is also used by the JVM, for comparisons the java flag `-Djava.locale.providers=CLDR` should be set. 75 | 76 | **Note:** Java 8 ships with an older CLDR version, specifically version 21. `scala-java-locales` uses the latest available version, hence there are some differences between the results and there are new available locales in `scala-java-locales`. 77 | 78 | ## Disclaimer 79 | 80 | Locales and the CLDR specifications are vast subjects. The locales in this project are as good as the data and the interpretation of the specification is. While the data and implementation has been tested as much as possible, it is possible and likely that there are errors. Please post an issue or submit a PR if you find such errors. 81 | 82 | In general the API attempts to behave be as close as possible to what happens on the JVM, e.g. the numeric system in Java seems to default to `latn` unless explicitly requested on the locale name. 83 | 84 | ## Demo 85 | A very simple `Scala.js` project is available at [demo](demo) 86 | 87 | ## Dependencies 88 | 89 | `scala-java-locales` explicitly doesn't have any dependencies. 90 | 91 | ## Contributors 92 | 93 | + Eric Peters [@er1c](https://github.com/er1c) 94 | + A. Alonso Dominguez [@alonsodomin](https://github.com/alonsodomin) 95 | + Marius B. Kotsbak [@mkotsbak](https://github.com/mkotsbak) 96 | + Timothy Klim [@TimothyKlim](https://github.com/TimothyKlim) 97 | + Andrea Peruffo [@andreaTP](https://github.com/AndreaTP) 98 | + Olli Helenius [@liff](https://github.com/liff) 99 | 100 | ## License 101 | 102 | Copyright © 2020 Carlos Quiroz 103 | 104 | `scala-java-locales` is distributed under the 105 | [BSD 3-Clause license](./LICENSE.txt). 106 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import locales._ 2 | import sbt.Keys._ 3 | import sbtcrossproject.CrossPlugin.autoImport.{ CrossType, crossProject } 4 | 5 | lazy val cldrApiVersion = "4.5.0" 6 | 7 | ThisBuild / versionScheme := Some("always") 8 | 9 | Global / onChangedBuildSource := ReloadOnSourceChanges 10 | 11 | lazy val scalaVersion213 = "2.13.16" 12 | lazy val scalaVersion3 = "3.3.5" 13 | ThisBuild / scalaVersion := scalaVersion213 14 | ThisBuild / crossScalaVersions := Seq("2.12.20", scalaVersion213, scalaVersion3) 15 | 16 | ThisBuild / githubWorkflowTargetTags ++= Seq("v*") 17 | ThisBuild / githubWorkflowPublishTargetBranches += 18 | RefPredicate.StartsWith(Ref.Tag("v")) 19 | 20 | lazy val java17 = JavaSpec.temurin("17") 21 | ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("8"), java17) 22 | 23 | ThisBuild / githubWorkflowBuildPreamble += 24 | WorkflowStep.Run( 25 | List("sudo apt-get install libutf8proc-dev"), 26 | name = Some("Install libutf8proc") 27 | ) 28 | ThisBuild / githubWorkflowPublish := Seq( 29 | WorkflowStep.Sbt( 30 | List("ci-release"), 31 | env = Map( 32 | "PGP_PASSPHRASE" -> "${{ secrets.PGP_PASSPHRASE }}", 33 | "PGP_SECRET" -> "${{ secrets.PGP_SECRET }}", 34 | "SONATYPE_PASSWORD" -> "${{ secrets.SONATYPE_PASSWORD }}", 35 | "SONATYPE_USERNAME" -> "${{ secrets.SONATYPE_USERNAME }}" 36 | ) 37 | ) 38 | ) 39 | 40 | ThisBuild / githubWorkflowBuildMatrixFailFast := Some(false) 41 | 42 | val commonSettings: Seq[Setting[_]] = Seq( 43 | scalacOptions ~= (_.filterNot( 44 | Set( 45 | "-Wdead-code", 46 | "-Ywarn-dead-code", 47 | "-Wunused:params", 48 | "-Ywarn-unused:params", 49 | "-Wvalue-discard", 50 | "-Ywarn-value-discard" 51 | ) 52 | )), 53 | Compile / doc / scalacOptions := Seq() 54 | ) 55 | 56 | inThisBuild( 57 | List( 58 | organization := "io.github.cquiroz", 59 | homepage := Some(url("https://github.com/cquiroz/scala-java-locales")), 60 | licenses := Seq("BSD 3-Clause License" -> url("https://opensource.org/licenses/BSD-3-Clause")), 61 | developers := List( 62 | Developer("cquiroz", 63 | "Carlos Quiroz", 64 | "carlos.m.quiroz@gmail.com", 65 | url("https://github.com/cquiroz") 66 | ), 67 | Developer("er1c", "Eric Peters", "", url("https://github.com/er1c")), 68 | Developer("alonsodomin", "A. Alonso Dominguez", "", url("https://github.com/alonsodomin")), 69 | Developer("mkotsbak", "Marius B. Kotsbak", "", url("https://github.com/mkotsbak")), 70 | Developer("TimothyKlim", "Timothy Klim", "", url("https://github.com/TimothyKlim")) 71 | ) 72 | ) 73 | ) 74 | 75 | lazy val root = project 76 | .in(file(".")) 77 | .settings(commonSettings) 78 | .settings( 79 | publish / skip := true 80 | ) 81 | .aggregate( 82 | core.js, 83 | core.jvm, 84 | core.native, 85 | tests.js, 86 | tests.jvm, 87 | tests.native, 88 | localesFullDb.js, 89 | localesFullDb.native, 90 | localesFullCurrenciesDb.js, 91 | localesFullCurrenciesDb.native, 92 | localesMinimalEnDb.js, 93 | localesMinimalEnDb.native, 94 | localesMinimalEnUSDb.js, 95 | localesMinimalEnUSDb.native, 96 | demo.js, 97 | demo.native 98 | ) 99 | 100 | lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) 101 | .crossType(CrossType.Full) 102 | .in(file("core")) 103 | .settings(commonSettings) 104 | .settings( 105 | name := "scala-java-locales", 106 | libraryDependencies ++= Seq( 107 | "io.github.cquiroz" %%% "cldr-api" % cldrApiVersion 108 | ), 109 | scalacOptions ~= (_.filterNot( 110 | Set( 111 | "-deprecation", 112 | "-Xfatal-warnings" 113 | ) 114 | )) 115 | ) 116 | .jsSettings( 117 | scalacOptions ++= { 118 | if (scalaVersion.value == scalaVersion3) 119 | Seq("-scalajs-genStaticForwardersForNonTopLevelObjects") 120 | else Seq("-P:scalajs:genStaticForwardersForNonTopLevelObjects") 121 | }, 122 | scalacOptions ++= { 123 | if (scalaVersion.value == scalaVersion3) Seq.empty 124 | else { 125 | val tagOrHash = 126 | if (isSnapshot.value) sys.process.Process("git rev-parse HEAD").lineStream_!.head 127 | else s"v${version.value}" 128 | (Compile / sourceDirectories).value.map { dir => 129 | val a = dir.toURI.toString 130 | val g = 131 | "https://raw.githubusercontent.com/cquiroz/scala-java-locales/" + tagOrHash + "/core/src/main/scala" 132 | s"-P:scalajs:mapSourceURI:$a->$g/" 133 | } 134 | } 135 | } 136 | ) 137 | .nativeSettings { 138 | scalacOptions += "-P:scalanative:genStaticForwardersForNonTopLevelObjects" 139 | } 140 | 141 | lazy val cldrDbVersion = "36.0" 142 | 143 | lazy val localesFullCurrenciesDb = crossProject(JSPlatform, NativePlatform) 144 | .in(file("localesFullCurrenciesDb")) 145 | .settings(commonSettings) 146 | .configure(_.enablePlugins(LocalesPlugin)) 147 | .settings( 148 | name := "locales-full-currencies-db", 149 | cldrVersion := CLDRVersion.Version(cldrDbVersion), 150 | localesFilter := LocalesFilter.All, 151 | nsFilter := NumberingSystemFilter.All, 152 | calendarFilter := CalendarFilter.All, 153 | currencyFilter := CurrencyFilter.All, 154 | supportDateTimeFormats := true, 155 | supportNumberFormats := true, 156 | supportISOCodes := true, 157 | libraryDependencies += ("org.portable-scala" %%% "portable-scala-reflect" % "1.1.3") 158 | .cross(CrossVersion.for3Use2_13) 159 | ) 160 | 161 | lazy val localesFullDb = crossProject(JSPlatform, NativePlatform) 162 | .crossType(CrossType.Pure) 163 | .in(file("localesFullDb")) 164 | .settings(commonSettings) 165 | .configure(_.enablePlugins(LocalesPlugin)) 166 | .settings( 167 | name := "locales-full-db", 168 | cldrVersion := CLDRVersion.Version(cldrDbVersion), 169 | localesFilter := LocalesFilter.All, 170 | nsFilter := NumberingSystemFilter.All, 171 | calendarFilter := CalendarFilter.All, 172 | currencyFilter := CurrencyFilter.None, 173 | supportDateTimeFormats := true, 174 | supportNumberFormats := true, 175 | supportISOCodes := true, 176 | libraryDependencies += ("org.portable-scala" %%% "portable-scala-reflect" % "1.1.3") 177 | .cross(CrossVersion.for3Use2_13) 178 | ) 179 | 180 | lazy val localesMinimalEnDb = crossProject(JSPlatform, NativePlatform) 181 | .in(file("localesMinimalEnDb")) 182 | .settings(commonSettings) 183 | .configure(_.enablePlugins(LocalesPlugin)) 184 | .settings( 185 | name := "locales-minimal-en-db", 186 | cldrVersion := CLDRVersion.Version(cldrDbVersion), 187 | localesFilter := LocalesFilter.Minimal, 188 | nsFilter := NumberingSystemFilter.Minimal, 189 | calendarFilter := CalendarFilter.Minimal, 190 | currencyFilter := CurrencyFilter.None, 191 | supportDateTimeFormats := true, 192 | supportNumberFormats := true, 193 | supportISOCodes := false, 194 | libraryDependencies += ("org.portable-scala" %%% "portable-scala-reflect" % "1.1.3") 195 | .cross(CrossVersion.for3Use2_13) 196 | ) 197 | 198 | lazy val localesMinimalEnUSDb = crossProject(JSPlatform, NativePlatform) 199 | .in(file("localesMinimalEnUSDb")) 200 | .settings(commonSettings) 201 | .configure(_.enablePlugins(LocalesPlugin)) 202 | .settings( 203 | name := "locales-minimal-en_US-db", 204 | cldrVersion := CLDRVersion.Version(cldrDbVersion), 205 | localesFilter := LocalesFilter.Selection(List("en_US")), 206 | nsFilter := NumberingSystemFilter.Minimal, 207 | calendarFilter := CalendarFilter.Minimal, 208 | currencyFilter := CurrencyFilter.None, 209 | supportDateTimeFormats := true, 210 | supportNumberFormats := true, 211 | supportISOCodes := false, 212 | libraryDependencies += ("org.portable-scala" %%% "portable-scala-reflect" % "1.1.3") 213 | .cross(CrossVersion.for3Use2_13) 214 | ) 215 | 216 | lazy val tests = crossProject(JVMPlatform, JSPlatform, NativePlatform) 217 | .in(file("tests")) 218 | .settings(commonSettings) 219 | .settings( 220 | publish / skip := true, 221 | name := "tests", 222 | libraryDependencies += "org.scalameta" %%% "munit" % "1.0.4" % Test, 223 | testFrameworks += new TestFramework("munit.Framework"), 224 | scalacOptions ~= (_.filterNot( 225 | Set( 226 | "-deprecation", 227 | "-Xfatal-warnings" 228 | ) 229 | )) 230 | ) 231 | .jsSettings(Test / parallelExecution := false, 232 | scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) 233 | ) 234 | .jsConfigure(_.dependsOn(core.js, macroutils, localesFullCurrenciesDb.js)) 235 | .jvmSettings( 236 | // Fork the JVM test to ensure that the custom flags are set 237 | Test / fork := true, 238 | // Use CLDR provider for locales 239 | // https://docs.oracle.com/javase/8/docs/technotes/guides/intl/enhancements.8.html#cldr 240 | Test / javaOptions ++= Seq( 241 | "-Duser.language=en", 242 | "-Duser.country=", 243 | "-Djava.locale.providers=CLDR", 244 | "-Dfile.encoding=UTF8", 245 | "-Xmx6G" 246 | ), 247 | libraryDependencies += "io.github.cquiroz" %%% "cldr-api" % cldrApiVersion, 248 | Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat 249 | ) 250 | .jvmConfigure(_.dependsOn(macroutils)) 251 | .nativeConfigure(_.dependsOn(core.native, macroutils, localesFullCurrenciesDb.native)) 252 | .platformsSettings(JSPlatform, NativePlatform)( 253 | Test / unmanagedSourceDirectories += baseDirectory.value.getParentFile / "js-native" / "src" / "test" / "scala" 254 | ) 255 | .platformsSettings(JSPlatform, JVMPlatform)( 256 | Test / unmanagedSourceDirectories += baseDirectory.value.getParentFile / "js-jvm" / "src" / "test" / "scala" 257 | ) 258 | 259 | lazy val macroutils = project 260 | .in(file("macroutils")) 261 | .settings(commonSettings) 262 | .settings( 263 | name := "macroutils", 264 | libraryDependencies ++= { 265 | if (scalaVersion.value == scalaVersion3) Seq.empty 266 | else Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value) 267 | }, 268 | scalacOptions ~= (_.filterNot( 269 | Set( 270 | "-deprecation", 271 | "-Xfatal-warnings" 272 | ) 273 | )), 274 | Compile / doc / sources := { 275 | if (scalaVersion.value == scalaVersion3) Seq() else (Compile / doc / sources).value 276 | } 277 | ) 278 | 279 | lazy val demo = crossProject(JSPlatform, NativePlatform) 280 | .in(file("demo")) 281 | .settings(commonSettings) 282 | .settings( 283 | publish / skip := true, 284 | scalaJSUseMainModuleInitializer := true, 285 | name := "demo" 286 | ) 287 | .dependsOn(core) 288 | -------------------------------------------------------------------------------- /core/js/src/main/scala-2/java/text/Normalizer.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import scala.scalajs.js 4 | 5 | object Normalizer { 6 | 7 | @inline def normalize(src: CharSequence, form: Form): String = 8 | if (src == null || form == null) 9 | throw new NullPointerException 10 | else 11 | src.toString().asInstanceOf[js.Dynamic].normalize(form.name()).asInstanceOf[String] 12 | 13 | @inline def isNormalized(src: CharSequence, form: Form): Boolean = 14 | normalize(src, form).contentEquals(src) 15 | 16 | final class Form private (name: String, ordinal: Int) extends Enum[Form](name, ordinal) 17 | 18 | object Form { 19 | final val NFC: Form = new Form("NFC", 0) 20 | final val NFD: Form = new Form("NFD", 1) 21 | final val NFKC: Form = new Form("NFKC", 2) 22 | final val NFKD: Form = new Form("NFKD", 3) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /core/js/src/main/scala-3/java/text/Normalizer.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import scala.scalajs.js 4 | 5 | object Normalizer { 6 | 7 | @inline def normalize(src: CharSequence, form: Form): String = 8 | if (src == null || form == null) 9 | throw new NullPointerException 10 | else 11 | src.toString().asInstanceOf[js.Dynamic].normalize(form.name()).asInstanceOf[String] 12 | 13 | @inline def isNormalized(src: CharSequence, form: Form): Boolean = 14 | normalize(src, form).contentEquals(src) 15 | 16 | enum Form extends java.lang.Enum[Form]: 17 | case NFC, NFD, NFKC, NFKD 18 | 19 | } 20 | -------------------------------------------------------------------------------- /core/js/src/main/scala/locales/DefaultLocale.scala: -------------------------------------------------------------------------------- 1 | package locales 2 | 3 | import java.util.Locale 4 | import scala.scalajs.js 5 | import scala.scalajs.js.annotation._ 6 | 7 | @js.native 8 | @JSGlobal 9 | class Navigator extends js.Any { 10 | def language: String = js.native 11 | } 12 | 13 | @js.native 14 | @JSGlobal 15 | class Window extends js.Any { 16 | def navigator: Navigator = js.native 17 | } 18 | 19 | object DefaultLocale { 20 | lazy val window: Window = js.Dynamic.global.window.asInstanceOf[Window] 21 | 22 | def platformLocale: Locale = { 23 | val lang = 24 | try 25 | // Attempt to read locale from the platform 26 | Some(window.navigator.language) 27 | catch { 28 | case _: Throwable => None 29 | } 30 | val l = lang.filter(LocalesDb.ldmls.contains).getOrElse("en") 31 | LocalesDb.localeForLanguageTag(l).getOrElse(LocalesDb.root.toLocale) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /core/jvm/src/main/scala/locales/DefaultLocale.scala: -------------------------------------------------------------------------------- 1 | package locales 2 | 3 | import java.util.Locale 4 | 5 | object DefaultLocale { 6 | def platformLocale: Locale = Locale.getDefault() 7 | } 8 | -------------------------------------------------------------------------------- /core/native/src/main/scala-2/java/text/Normalizer.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | object Normalizer { 4 | 5 | def normalize(src: CharSequence, form: Form): String = 6 | NormalizerImpl.normalize(src, form) 7 | 8 | def isNormalized(src: CharSequence, form: Form): Boolean = 9 | normalize(src, form).contentEquals(src) 10 | 11 | final class Form private (name: String, ordinal: Int) extends Enum[Form](name, ordinal) 12 | 13 | object Form { 14 | final val NFC: Form = new Form("NFC", 0) 15 | final val NFD: Form = new Form("NFD", 1) 16 | final val NFKC: Form = new Form("NFKC", 2) 17 | final val NFKD: Form = new Form("NFKD", 3) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /core/native/src/main/scala-3/java/text/Normalizer.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | object Normalizer { 4 | 5 | def normalize(src: CharSequence, form: Form): String = 6 | NormalizerImpl.normalize(src, form) 7 | 8 | def isNormalized(src: CharSequence, form: Form): Boolean = 9 | normalize(src, form).contentEquals(src) 10 | 11 | enum Form extends java.lang.Enum[Form]: 12 | case NFC, NFD, NFKC, NFKD 13 | 14 | } 15 | -------------------------------------------------------------------------------- /core/native/src/main/scala/locales/DefaultLocale.scala: -------------------------------------------------------------------------------- 1 | package locales 2 | 3 | import java.util.Locale 4 | 5 | object DefaultLocale { 6 | def platformLocale: Locale = { 7 | val lang = System.getProperty("user.language") 8 | val countrySuffix = System.getProperty("user.country") match { 9 | case "" => "" 10 | case s => s"_$s" 11 | } 12 | 13 | val localeString = s"$lang$countrySuffix" 14 | 15 | LocalesDb.localeForLanguageTag(localeString).getOrElse(LocalesDb.root.toLocale) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/native/src/main/scala/locales/NormalizerImpl.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import java.nio.CharBuffer 4 | import java.nio.charset.Charset 5 | import scala.scalanative.libc.stdlib 6 | import scala.scalanative.unsafe._ 7 | 8 | import Normalizer._ 9 | 10 | private object NormalizerImpl { 11 | 12 | def normalize(src: CharSequence, form: Form): String = 13 | if (src == null || form == null) 14 | throw new NullPointerException 15 | else 16 | Zone.acquire { implicit z => 17 | import Form._ 18 | import utf8proc._ 19 | 20 | val cstr = form match { 21 | case NFC => utf8proc_NFC(charSeqToCString(src)) 22 | case NFD => utf8proc_NFD(charSeqToCString(src)) 23 | case NFKC => utf8proc_NFKC(charSeqToCString(src)) 24 | case NFKD => utf8proc_NFKD(charSeqToCString(src)) 25 | } 26 | 27 | val normalized = fromCString(cstr) // TODO can be further optimized 28 | stdlib.free(cstr) 29 | normalized 30 | } 31 | 32 | private def charSeqToCString(cs: CharSequence)(implicit z: Zone): CString = cs match { 33 | case str: String => toCString(str) 34 | case str => 35 | import scalanative.unsigned._ 36 | 37 | val encoder = Charset.defaultCharset().newEncoder() 38 | val cb = CharBuffer.wrap(str) 39 | val bb = encoder.encode(cb) 40 | 41 | val n = bb.limit() 42 | val cstr = z.alloc((n + 1).toULong) 43 | 44 | var i = 0 45 | while (i < n) { 46 | !(cstr + i.toLong) = bb.get(i) 47 | i += 1 48 | } 49 | !(cstr + i.toLong) = 0.toByte 50 | 51 | cstr 52 | } 53 | 54 | } 55 | 56 | @link("utf8proc") 57 | @extern 58 | private object utf8proc { 59 | def utf8proc_NFD(str: CString): CString = extern 60 | def utf8proc_NFC(str: CString): CString = extern 61 | def utf8proc_NFKD(str: CString): CString = extern 62 | def utf8proc_NFKC(str: CString): CString = extern 63 | } 64 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.11/scala/collection/compat/CompatImpl.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.collection.compat 14 | 15 | import scala.reflect.ClassTag 16 | import scala.collection.generic.CanBuildFrom 17 | import scala.collection.{ immutable => i, mutable => m } 18 | 19 | /* builder optimized for a single ++= call, which returns identity on result if possible 20 | * and defers to the underlying builder if not. 21 | */ 22 | private final class IdentityPreservingBuilder[A, CC[X] <: TraversableOnce[X]]( 23 | that: m.Builder[A, CC[A]] 24 | )(implicit ct: ClassTag[CC[A]]) 25 | extends m.Builder[A, CC[A]] { 26 | 27 | // invariant: ruined => (collection == null) 28 | var collection: CC[A] = null.asInstanceOf[CC[A]] 29 | var ruined = false 30 | 31 | private[this] def ruin(): Unit = { 32 | if (collection != null) that ++= collection 33 | collection = null.asInstanceOf[CC[A]] 34 | ruined = true 35 | } 36 | 37 | override def ++=(elems: TraversableOnce[A]): this.type = 38 | elems match { 39 | case ct(ca) if collection == null && !ruined => 40 | collection = ca 41 | this 42 | case _ => 43 | ruin() 44 | that ++= elems 45 | this 46 | } 47 | 48 | def +=(elem: A): this.type = { 49 | ruin() 50 | that += elem 51 | this 52 | } 53 | 54 | def clear(): Unit = { 55 | collection = null.asInstanceOf[CC[A]] 56 | if (ruined) that.clear() 57 | ruined = false 58 | } 59 | 60 | def result(): CC[A] = if (collection == null) that.result() else collection 61 | } 62 | 63 | private[compat] object CompatImpl { 64 | def simpleCBF[A, C](f: => m.Builder[A, C]): CanBuildFrom[Any, A, C] = 65 | new CanBuildFrom[Any, A, C] { 66 | def apply(from: Any): m.Builder[A, C] = apply() 67 | def apply(): m.Builder[A, C] = f 68 | } 69 | 70 | type ImmutableBitSetCC[X] = ({ type L[_] = i.BitSet })#L[X] 71 | type MutableBitSetCC[X] = ({ type L[_] = m.BitSet })#L[X] 72 | } 73 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.11/scala/collection/compat/PackageShared.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.collection.compat 14 | 15 | import scala.collection.generic._ 16 | import scala.reflect.ClassTag 17 | import scala.collection.{ 18 | BitSet, 19 | GenTraversable, 20 | IterableLike, 21 | IterableView, 22 | MapLike, 23 | TraversableLike, 24 | immutable => i, 25 | mutable => m 26 | } 27 | import scala.runtime.{ Tuple2Zipped, Tuple3Zipped } 28 | import scala.{ collection => c } 29 | 30 | /** The collection compatibility API */ 31 | private[compat] trait PackageShared { 32 | import CompatImpl._ 33 | 34 | /** A factory that builds a collection of type `C` with elements of type `A`. 35 | * 36 | * @tparam A 37 | * Type of elements (e.g. `Int`, `Boolean`, etc.) 38 | * @tparam C 39 | * Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) 40 | */ 41 | type Factory[-A, +C] = CanBuildFrom[Nothing, A, C] 42 | 43 | implicit class FactoryOps[-A, +C](private val factory: Factory[A, C]) { 44 | 45 | /** @return 46 | * A collection of type `C` containing the same elements as the source collection `it`. 47 | * @param it 48 | * Source collection 49 | */ 50 | def fromSpecific(it: TraversableOnce[A]): C = (factory() ++= it).result() 51 | 52 | /** Get a Builder for the collection. For non-strict collection types this will use an 53 | * intermediate buffer. Building collections with `fromSpecific` is preferred because it can be 54 | * lazy for lazy collections. 55 | */ 56 | def newBuilder: m.Builder[A, C] = factory() 57 | } 58 | 59 | implicit def genericCompanionToCBF[A, CC[X] <: GenTraversable[X]]( 60 | fact: GenericCompanion[CC] 61 | ): CanBuildFrom[Any, A, CC[A]] = { 62 | /* see https://github.com/scala/scala-collection-compat/issues/337 63 | `simpleCBF.apply` takes a by-name parameter and relies on 64 | repeated references generating new builders, thus this expression 65 | must be non-strict 66 | */ 67 | def builder: m.Builder[A, CC[A]] = fact match { 68 | case c.Seq | i.Seq => new IdentityPreservingBuilder[A, i.Seq](i.Seq.newBuilder[A]) 69 | case c.LinearSeq | i.LinearSeq => 70 | new IdentityPreservingBuilder[A, i.LinearSeq](i.LinearSeq.newBuilder[A]) 71 | case _ => fact.newBuilder[A] 72 | } 73 | simpleCBF(builder) 74 | } 75 | 76 | implicit def sortedSetCompanionToCBF[ 77 | A: Ordering, 78 | CC[X] <: c.SortedSet[X] with c.SortedSetLike[X, CC[X]] 79 | ](fact: SortedSetFactory[CC]): CanBuildFrom[Any, A, CC[A]] = 80 | simpleCBF(fact.newBuilder[A]) 81 | 82 | implicit def arrayCompanionToCBF[A: ClassTag](fact: Array.type): CanBuildFrom[Any, A, Array[A]] = 83 | simpleCBF(Array.newBuilder[A]) 84 | 85 | implicit def mapFactoryToCBF[K, V, CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]]( 86 | fact: MapFactory[CC] 87 | ): CanBuildFrom[Any, (K, V), CC[K, V]] = 88 | simpleCBF(fact.newBuilder[K, V]) 89 | 90 | implicit def sortedMapFactoryToCBF[ 91 | K: Ordering, 92 | V, 93 | CC[A, B] <: c.SortedMap[A, B] with c.SortedMapLike[A, B, CC[A, B]] 94 | ](fact: SortedMapFactory[CC]): CanBuildFrom[Any, (K, V), CC[K, V]] = 95 | simpleCBF(fact.newBuilder[K, V]) 96 | 97 | implicit def bitSetFactoryToCBF(fact: BitSetFactory[BitSet]): CanBuildFrom[Any, Int, BitSet] = 98 | simpleCBF(fact.newBuilder) 99 | 100 | implicit def immutableBitSetFactoryToCBF( 101 | fact: BitSetFactory[i.BitSet] 102 | ): CanBuildFrom[Any, Int, ImmutableBitSetCC[Int]] = 103 | simpleCBF(fact.newBuilder) 104 | 105 | implicit def mutableBitSetFactoryToCBF( 106 | fact: BitSetFactory[m.BitSet] 107 | ): CanBuildFrom[Any, Int, MutableBitSetCC[Int]] = 108 | simpleCBF(fact.newBuilder) 109 | 110 | implicit class IterableFactoryExtensionMethods[CC[X] <: GenTraversable[X]]( 111 | private val fact: GenericCompanion[CC] 112 | ) { 113 | def from[A](source: TraversableOnce[A]): CC[A] = 114 | fact.apply(source.toSeq: _*) 115 | } 116 | 117 | implicit class MapFactoryExtensionMethods[CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]]( 118 | private val fact: MapFactory[CC] 119 | ) { 120 | def from[K, V](source: TraversableOnce[(K, V)]): CC[K, V] = 121 | fact.apply(source.toSeq: _*) 122 | } 123 | 124 | implicit class BitSetFactoryExtensionMethods[ 125 | C <: scala.collection.BitSet with scala.collection.BitSetLike[C] 126 | ](private val fact: BitSetFactory[C]) { 127 | def fromSpecific(source: TraversableOnce[Int]): C = 128 | fact.apply(source.toSeq: _*) 129 | } 130 | 131 | private[compat] def build[T, CC](builder: m.Builder[T, CC], source: TraversableOnce[T]): CC = { 132 | builder ++= source 133 | builder.result() 134 | } 135 | 136 | implicit def toImmutableSortedMapExtensions( 137 | fact: i.SortedMap.type 138 | ): ImmutableSortedMapExtensions = 139 | new ImmutableSortedMapExtensions(fact) 140 | 141 | implicit def toImmutableListMapExtensions(fact: i.ListMap.type): ImmutableListMapExtensions = 142 | new ImmutableListMapExtensions(fact) 143 | 144 | implicit def toImmutableHashMapExtensions(fact: i.HashMap.type): ImmutableHashMapExtensions = 145 | new ImmutableHashMapExtensions(fact) 146 | 147 | implicit def toImmutableTreeMapExtensions(fact: i.TreeMap.type): ImmutableTreeMapExtensions = 148 | new ImmutableTreeMapExtensions(fact) 149 | 150 | implicit def toImmutableIntMapExtensions(fact: i.IntMap.type): ImmutableIntMapExtensions = 151 | new ImmutableIntMapExtensions(fact) 152 | 153 | implicit def toImmutableLongMapExtensions(fact: i.LongMap.type): ImmutableLongMapExtensions = 154 | new ImmutableLongMapExtensions(fact) 155 | 156 | implicit def toMutableLongMapExtensions(fact: m.LongMap.type): MutableLongMapExtensions = 157 | new MutableLongMapExtensions(fact) 158 | 159 | implicit def toMutableHashMapExtensions(fact: m.HashMap.type): MutableHashMapExtensions = 160 | new MutableHashMapExtensions(fact) 161 | 162 | implicit def toMutableListMapExtensions(fact: m.ListMap.type): MutableListMapExtensions = 163 | new MutableListMapExtensions(fact) 164 | 165 | implicit def toMutableMapExtensions(fact: m.Map.type): MutableMapExtensions = 166 | new MutableMapExtensions(fact) 167 | 168 | implicit def toStreamExtensionMethods[A](stream: Stream[A]): StreamExtensionMethods[A] = 169 | new StreamExtensionMethods[A](stream) 170 | 171 | implicit def toSortedExtensionMethods[K, V <: Sorted[K, V]]( 172 | fact: Sorted[K, V] 173 | ): SortedExtensionMethods[K, V] = 174 | new SortedExtensionMethods[K, V](fact) 175 | 176 | implicit def toIteratorExtensionMethods[A](self: Iterator[A]): IteratorExtensionMethods[A] = 177 | new IteratorExtensionMethods[A](self) 178 | 179 | implicit def toTraversableExtensionMethods[A]( 180 | self: Traversable[A] 181 | ): TraversableExtensionMethods[A] = 182 | new TraversableExtensionMethods[A](self) 183 | 184 | implicit def toTraversableOnceExtensionMethods[A]( 185 | self: TraversableOnce[A] 186 | ): TraversableOnceExtensionMethods[A] = 187 | new TraversableOnceExtensionMethods[A](self) 188 | 189 | // This really belongs into scala.collection but there's already a package object 190 | // in scala-library so we can't add to it 191 | type IterableOnce[+X] = c.TraversableOnce[X] 192 | val IterableOnce = c.TraversableOnce 193 | 194 | implicit def toMapExtensionMethods[K, V]( 195 | self: scala.collection.Map[K, V] 196 | ): MapExtensionMethods[K, V] = 197 | new MapExtensionMethods[K, V](self) 198 | 199 | implicit def toMapViewExtensionMethods[K, V, C <: scala.collection.Map[K, V]]( 200 | self: IterableView[(K, V), C] 201 | ): MapViewExtensionMethods[K, V, C] = 202 | new MapViewExtensionMethods[K, V, C](self) 203 | } 204 | 205 | class ImmutableSortedMapExtensions(private val fact: i.SortedMap.type) extends AnyVal { 206 | def from[K: Ordering, V](source: TraversableOnce[(K, V)]): i.SortedMap[K, V] = 207 | build(i.SortedMap.newBuilder[K, V], source) 208 | } 209 | 210 | class ImmutableListMapExtensions(private val fact: i.ListMap.type) extends AnyVal { 211 | def from[K, V](source: TraversableOnce[(K, V)]): i.ListMap[K, V] = 212 | build(i.ListMap.newBuilder[K, V], source) 213 | } 214 | 215 | class ImmutableHashMapExtensions(private val fact: i.HashMap.type) extends AnyVal { 216 | def from[K, V](source: TraversableOnce[(K, V)]): i.HashMap[K, V] = 217 | build(i.HashMap.newBuilder[K, V], source) 218 | } 219 | 220 | class ImmutableTreeMapExtensions(private val fact: i.TreeMap.type) extends AnyVal { 221 | def from[K: Ordering, V](source: TraversableOnce[(K, V)]): i.TreeMap[K, V] = 222 | build(i.TreeMap.newBuilder[K, V], source) 223 | } 224 | 225 | class ImmutableIntMapExtensions(private val fact: i.IntMap.type) extends AnyVal { 226 | def from[V](source: TraversableOnce[(Int, V)]): i.IntMap[V] = 227 | build(i.IntMap.canBuildFrom[Int, V](), source) 228 | } 229 | 230 | class ImmutableLongMapExtensions(private val fact: i.LongMap.type) extends AnyVal { 231 | def from[V](source: TraversableOnce[(Long, V)]): i.LongMap[V] = 232 | build(i.LongMap.canBuildFrom[Long, V](), source) 233 | } 234 | 235 | class MutableLongMapExtensions(private val fact: m.LongMap.type) extends AnyVal { 236 | def from[V](source: TraversableOnce[(Long, V)]): m.LongMap[V] = 237 | build(m.LongMap.canBuildFrom[Long, V](), source) 238 | } 239 | 240 | class MutableHashMapExtensions(private val fact: m.HashMap.type) extends AnyVal { 241 | def from[K, V](source: TraversableOnce[(K, V)]): m.HashMap[K, V] = 242 | build(m.HashMap.canBuildFrom[K, V](), source) 243 | } 244 | 245 | class MutableListMapExtensions(private val fact: m.ListMap.type) extends AnyVal { 246 | def from[K, V](source: TraversableOnce[(K, V)]): m.ListMap[K, V] = 247 | build(m.ListMap.canBuildFrom[K, V](), source) 248 | } 249 | 250 | class MutableMapExtensions(private val fact: m.Map.type) extends AnyVal { 251 | def from[K, V](source: TraversableOnce[(K, V)]): m.Map[K, V] = 252 | build(m.Map.canBuildFrom[K, V](), source) 253 | } 254 | 255 | class StreamExtensionMethods[A](private val stream: Stream[A]) extends AnyVal { 256 | def lazyAppendedAll(as: => TraversableOnce[A]): Stream[A] = stream.append(as) 257 | } 258 | 259 | class SortedExtensionMethods[K, T <: Sorted[K, T]](private val fact: Sorted[K, T]) { 260 | def rangeFrom(from: K): T = fact.from(from) 261 | def rangeTo(to: K): T = fact.to(to) 262 | def rangeUntil(until: K): T = fact.until(until) 263 | } 264 | 265 | class IteratorExtensionMethods[A](private val self: c.Iterator[A]) extends AnyVal { 266 | def sameElements[B >: A](that: c.TraversableOnce[B]): Boolean = 267 | self.sameElements(that.iterator) 268 | def concat[B >: A](that: c.TraversableOnce[B]): c.TraversableOnce[B] = self ++ that 269 | def tapEach[U](f: A => U): c.Iterator[A] = self.map { a => f(a); a } 270 | } 271 | 272 | class TraversableOnceExtensionMethods[A](private val self: c.TraversableOnce[A]) extends AnyVal { 273 | def iterator: Iterator[A] = self.toIterator 274 | 275 | def minOption[B >: A](implicit ord: Ordering[B]): Option[A] = 276 | if (self.isEmpty) 277 | None 278 | else 279 | Some(self.min(ord)) 280 | 281 | def maxOption[B >: A](implicit ord: Ordering[B]): Option[A] = 282 | if (self.isEmpty) 283 | None 284 | else 285 | Some(self.max(ord)) 286 | 287 | def minByOption[B](f: A => B)(implicit cmp: Ordering[B]): Option[A] = 288 | if (self.isEmpty) 289 | None 290 | else 291 | Some(self.minBy(f)(cmp)) 292 | 293 | def maxByOption[B](f: A => B)(implicit cmp: Ordering[B]): Option[A] = 294 | if (self.isEmpty) 295 | None 296 | else 297 | Some(self.maxBy(f)(cmp)) 298 | } 299 | 300 | class TraversableExtensionMethods[A](private val self: c.Traversable[A]) extends AnyVal { 301 | def iterableFactory: GenericCompanion[Traversable] = self.companion 302 | 303 | def sizeCompare(otherSize: Int): Int = SizeCompareImpl.sizeCompareInt(self)(otherSize) 304 | def sizeIs: SizeCompareOps = new SizeCompareOps(self) 305 | def sizeCompare(that: c.Traversable[_]): Int = SizeCompareImpl.sizeCompareColl(self)(that) 306 | 307 | } 308 | 309 | class SeqExtensionMethods[A](private val self: c.Seq[A]) extends AnyVal { 310 | def lengthIs: SizeCompareOps = new SizeCompareOps(self) 311 | } 312 | 313 | class SizeCompareOps private[compat] (private val it: c.Traversable[_]) extends AnyVal { 314 | import SizeCompareImpl._ 315 | 316 | /** Tests if the size of the collection is less than some value. */ 317 | @inline def <(size: Int): Boolean = sizeCompareInt(it)(size) < 0 318 | 319 | /** Tests if the size of the collection is less than or equal to some value. */ 320 | @inline def <=(size: Int): Boolean = sizeCompareInt(it)(size) <= 0 321 | 322 | /** Tests if the size of the collection is equal to some value. */ 323 | @inline def ==(size: Int): Boolean = sizeCompareInt(it)(size) == 0 324 | 325 | /** Tests if the size of the collection is not equal to some value. */ 326 | @inline def !=(size: Int): Boolean = sizeCompareInt(it)(size) != 0 327 | 328 | /** Tests if the size of the collection is greater than or equal to some value. */ 329 | @inline def >=(size: Int): Boolean = sizeCompareInt(it)(size) >= 0 330 | 331 | /** Tests if the size of the collection is greater than some value. */ 332 | @inline def >(size: Int): Boolean = sizeCompareInt(it)(size) > 0 333 | } 334 | 335 | private object SizeCompareImpl { 336 | def sizeCompareInt(self: c.Traversable[_])(otherSize: Int): Int = 337 | self match { 338 | case self: c.SeqLike[_, _] => self.lengthCompare(otherSize) 339 | case _ => 340 | if (otherSize < 0) 1 341 | else { 342 | var i = 0 343 | val it = self.toIterator 344 | while (it.hasNext) { 345 | if (i == otherSize) return 1 346 | it.next() 347 | i += 1 348 | } 349 | i - otherSize 350 | } 351 | } 352 | 353 | // `IndexedSeq` is the only thing that we can safely say has a known size 354 | def sizeCompareColl(self: c.Traversable[_])(that: c.Traversable[_]): Int = 355 | that match { 356 | case that: c.IndexedSeq[_] => sizeCompareInt(self)(that.length) 357 | case _ => 358 | self match { 359 | case self: c.IndexedSeq[_] => 360 | val res = sizeCompareInt(that)(self.length) 361 | // can't just invert the result, because `-Int.MinValue == Int.MinValue` 362 | if (res == Int.MinValue) 1 else -res 363 | case _ => 364 | val thisIt = self.toIterator 365 | val thatIt = that.toIterator 366 | while (thisIt.hasNext && thatIt.hasNext) { 367 | thisIt.next() 368 | thatIt.next() 369 | } 370 | java.lang.Boolean.compare(thisIt.hasNext, thatIt.hasNext) 371 | } 372 | } 373 | } 374 | 375 | class TraversableLikeExtensionMethods[A, Repr](private val self: c.GenTraversableLike[A, Repr]) 376 | extends AnyVal { 377 | def tapEach[U](f: A => U)(implicit bf: CanBuildFrom[Repr, A, Repr]): Repr = 378 | self.map { a => f(a); a } 379 | 380 | def partitionMap[A1, A2, That, Repr1, Repr2](f: A => Either[A1, A2])(implicit 381 | bf1: CanBuildFrom[Repr, A1, Repr1], 382 | bf2: CanBuildFrom[Repr, A2, Repr2] 383 | ): (Repr1, Repr2) = { 384 | val l = bf1() 385 | val r = bf2() 386 | self.foreach { x => 387 | f(x) match { 388 | case Left(x1) => l += x1 389 | case Right(x2) => r += x2 390 | } 391 | } 392 | (l.result(), r.result()) 393 | } 394 | 395 | def groupMap[K, B, That]( 396 | key: A => K 397 | )(f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): Map[K, That] = { 398 | val map = m.Map.empty[K, m.Builder[B, That]] 399 | for (elem <- self) { 400 | val k = key(elem) 401 | val bldr = map.getOrElseUpdate(k, bf(self.repr)) 402 | bldr += f(elem) 403 | } 404 | val res = Map.newBuilder[K, That] 405 | for ((k, bldr) <- map) res += ((k, bldr.result())) 406 | res.result() 407 | } 408 | 409 | def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): Map[K, B] = { 410 | val map = m.Map.empty[K, B] 411 | for (elem <- self) { 412 | val k = key(elem) 413 | val v = map.get(k) match { 414 | case Some(b) => reduce(b, f(elem)) 415 | case None => f(elem) 416 | } 417 | map.put(k, v) 418 | } 419 | map.toMap 420 | } 421 | } 422 | 423 | class TrulyTraversableLikeExtensionMethods[El1, Repr1]( 424 | private val self: TraversableLike[El1, Repr1] 425 | ) extends AnyVal { 426 | 427 | def lazyZip[El2, Repr2, T2](t2: T2)(implicit 428 | w2: T2 => IterableLike[El2, Repr2] 429 | ): Tuple2Zipped[El1, Repr1, El2, Repr2] = new Tuple2Zipped((self, t2)) 430 | } 431 | 432 | class Tuple2ZippedExtensionMethods[El1, Repr1, El2, Repr2]( 433 | private val self: Tuple2Zipped[El1, Repr1, El2, Repr2] 434 | ) { 435 | 436 | def lazyZip[El3, Repr3, T3](t3: T3)(implicit 437 | w3: T3 => IterableLike[El3, Repr3] 438 | ): Tuple3Zipped[El1, Repr1, El2, Repr2, El3, Repr3] = 439 | new Tuple3Zipped((self.colls._1, self.colls._2, t3)) 440 | } 441 | 442 | class MapExtensionMethods[K, V](private val self: scala.collection.Map[K, V]) extends AnyVal { 443 | 444 | def foreachEntry[U](f: (K, V) => U): Unit = 445 | self.foreach { case (k, v) => f(k, v) } 446 | 447 | } 448 | 449 | class MapViewExtensionMethods[K, V, C <: scala.collection.Map[K, V]]( 450 | private val self: IterableView[(K, V), C] 451 | ) extends AnyVal { 452 | 453 | def mapValues[W, That](f: V => W)(implicit 454 | bf: CanBuildFrom[IterableView[(K, V), C], (K, W), That] 455 | ): That = 456 | self.map[(K, W), That] { case (k, v) => (k, f(v)) } 457 | 458 | // TODO: Replace the current implementation of `mapValues` with this 459 | // after major version bump when bincompat can be broken. 460 | // At the same time, remove `canBuildFromIterableViewMapLike` 461 | /* 462 | def mapValues[W](f: V => W): IterableView[(K, W), C] = 463 | // the implementation of `self.map` also casts the result 464 | self.map({ case (k, v) => (k, f(v)) }).asInstanceOf[IterableView[(K, W), C]] 465 | */ 466 | 467 | def filterKeys(p: K => Boolean): IterableView[(K, V), C] = 468 | self.filter { case (k, _) => p(k) } 469 | } 470 | 471 | class ImmutableQueueExtensionMethods[A](private val self: i.Queue[A]) extends AnyVal { 472 | def enqueueAll[B >: A](iter: c.Iterable[B]): i.Queue[B] = 473 | self.enqueue(iter.to[i.Iterable]) 474 | } 475 | 476 | class MutableQueueExtensionMethods[Element](private val self: m.Queue[Element]) extends AnyVal { 477 | def enqueueAll(iter: c.Iterable[Element]): Unit = 478 | self.enqueue(iter.toIndexedSeq: _*) 479 | } 480 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.11/scala/collection/compat/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.collection 14 | 15 | import scala.collection.generic.{ CanBuildFrom, GenericOrderedCompanion, IsTraversableLike } 16 | 17 | import scala.runtime.Tuple2Zipped 18 | import scala.collection.{ immutable => i, mutable => m } 19 | import scala.{ collection => c } 20 | 21 | package object compat extends compat.PackageShared { 22 | implicit def genericOrderedCompanionToCBF[A, CC[X] <: Traversable[X]]( 23 | fact: GenericOrderedCompanion[CC] 24 | )(implicit ordering: Ordering[A]): CanBuildFrom[Any, A, CC[A]] = 25 | CompatImpl.simpleCBF(fact.newBuilder[A]) 26 | 27 | // CanBuildFrom instances for `IterableView[(K, V), Map[K, V]]` that preserve 28 | // the strict type of the view to be `Map` instead of `Iterable` 29 | // Instances produced by this method are used to chain `filterKeys` after `mapValues` 30 | implicit def canBuildFromIterableViewMapLike[K, V, L, W, CC[X, Y] <: Map[X, Y]] 31 | : CanBuildFrom[IterableView[(K, V), CC[K, V]], (L, W), IterableView[(L, W), CC[L, W]]] = 32 | new CanBuildFrom[IterableView[(K, V), CC[K, V]], (L, W), IterableView[(L, W), CC[L, W]]] { 33 | // `CanBuildFrom` parameters are used as type constraints, they are not used 34 | // at run-time, hence the dummy builder implementations 35 | def apply(from: IterableView[(K, V), CC[K, V]]) = new TraversableView.NoBuilder 36 | def apply() = new TraversableView.NoBuilder 37 | } 38 | 39 | implicit def toTraversableLikeExtensionMethods[Repr](self: Repr)(implicit 40 | traversable: IsTraversableLike[Repr] 41 | ): TraversableLikeExtensionMethods[traversable.A, Repr] = 42 | new TraversableLikeExtensionMethods[traversable.A, Repr](traversable.conversion(self)) 43 | 44 | implicit def toSeqExtensionMethods[A](self: c.Seq[A]): SeqExtensionMethods[A] = 45 | new SeqExtensionMethods[A](self) 46 | 47 | implicit def toTrulyTraversableLikeExtensionMethods[T1, El1, Repr1](self: T1)(implicit 48 | w1: T1 => TraversableLike[El1, Repr1] 49 | ): TrulyTraversableLikeExtensionMethods[El1, Repr1] = 50 | new TrulyTraversableLikeExtensionMethods[El1, Repr1](w1(self)) 51 | 52 | implicit def toTuple2ZippedExtensionMethods[El1, Repr1, El2, Repr2]( 53 | self: Tuple2Zipped[El1, Repr1, El2, Repr2] 54 | ): Tuple2ZippedExtensionMethods[El1, Repr1, El2, Repr2] = 55 | new Tuple2ZippedExtensionMethods[El1, Repr1, El2, Repr2](self) 56 | 57 | implicit def toImmutableQueueExtensionMethods[A]( 58 | self: i.Queue[A] 59 | ): ImmutableQueueExtensionMethods[A] = 60 | new ImmutableQueueExtensionMethods[A](self) 61 | 62 | implicit def toMutableQueueExtensionMethods[A]( 63 | self: m.Queue[A] 64 | ): MutableQueueExtensionMethods[A] = 65 | new MutableQueueExtensionMethods[A](self) 66 | 67 | } 68 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.11/scala/jdk/CollectionConverters.scala: -------------------------------------------------------------------------------- 1 | package scala.jdk 2 | 3 | import scala.collection.convert.{ DecorateAsJava, DecorateAsScala } 4 | 5 | object CollectionConverters extends DecorateAsJava with DecorateAsScala 6 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.12/scala/collection/compat/CompatImpl.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.collection.compat 14 | 15 | import scala.reflect.ClassTag 16 | import scala.collection.generic.CanBuildFrom 17 | import scala.collection.{ immutable => i, mutable => m } 18 | 19 | /* builder optimized for a single ++= call, which returns identity on result if possible 20 | * and defers to the underlying builder if not. 21 | */ 22 | private final class IdentityPreservingBuilder[A, CC[X] <: TraversableOnce[X]]( 23 | that: m.Builder[A, CC[A]] 24 | )(implicit ct: ClassTag[CC[A]]) 25 | extends m.Builder[A, CC[A]] { 26 | 27 | // invariant: ruined => (collection == null) 28 | var collection: CC[A] = null.asInstanceOf[CC[A]] 29 | var ruined = false 30 | 31 | private[this] def ruin(): Unit = { 32 | if (collection != null) that ++= collection 33 | collection = null.asInstanceOf[CC[A]] 34 | ruined = true 35 | } 36 | 37 | override def ++=(elems: TraversableOnce[A]): this.type = 38 | elems match { 39 | case ct(ca) if collection == null && !ruined => 40 | collection = ca 41 | this 42 | case _ => 43 | ruin() 44 | that ++= elems 45 | this 46 | } 47 | 48 | def +=(elem: A): this.type = { 49 | ruin() 50 | that += elem 51 | this 52 | } 53 | 54 | def clear(): Unit = { 55 | collection = null.asInstanceOf[CC[A]] 56 | if (ruined) that.clear() 57 | ruined = false 58 | } 59 | 60 | def result(): CC[A] = if (collection == null) that.result() else collection 61 | } 62 | 63 | private[compat] object CompatImpl { 64 | def simpleCBF[A, C](f: => m.Builder[A, C]): CanBuildFrom[Any, A, C] = 65 | new CanBuildFrom[Any, A, C] { 66 | def apply(from: Any): m.Builder[A, C] = apply() 67 | def apply(): m.Builder[A, C] = f 68 | } 69 | 70 | type ImmutableBitSetCC[X] = ({ type L[_] = i.BitSet })#L[X] 71 | type MutableBitSetCC[X] = ({ type L[_] = m.BitSet })#L[X] 72 | } 73 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.12/scala/collection/compat/PackageShared.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.collection.compat 14 | 15 | import scala.collection.generic._ 16 | import scala.reflect.ClassTag 17 | import scala.collection.{ 18 | BitSet, 19 | GenTraversable, 20 | IterableLike, 21 | IterableView, 22 | MapLike, 23 | TraversableLike, 24 | immutable => i, 25 | mutable => m 26 | } 27 | import scala.runtime.{ Tuple2Zipped, Tuple3Zipped } 28 | import scala.{ collection => c } 29 | 30 | /** The collection compatibility API */ 31 | private[compat] trait PackageShared { 32 | import CompatImpl._ 33 | 34 | /** A factory that builds a collection of type `C` with elements of type `A`. 35 | * 36 | * @tparam A 37 | * Type of elements (e.g. `Int`, `Boolean`, etc.) 38 | * @tparam C 39 | * Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) 40 | */ 41 | type Factory[-A, +C] = CanBuildFrom[Nothing, A, C] 42 | 43 | implicit class FactoryOps[-A, +C](private val factory: Factory[A, C]) { 44 | 45 | /** @return 46 | * A collection of type `C` containing the same elements as the source collection `it`. 47 | * @param it 48 | * Source collection 49 | */ 50 | def fromSpecific(it: TraversableOnce[A]): C = (factory() ++= it).result() 51 | 52 | /** Get a Builder for the collection. For non-strict collection types this will use an 53 | * intermediate buffer. Building collections with `fromSpecific` is preferred because it can be 54 | * lazy for lazy collections. 55 | */ 56 | def newBuilder: m.Builder[A, C] = factory() 57 | } 58 | 59 | implicit def genericCompanionToCBF[A, CC[X] <: GenTraversable[X]]( 60 | fact: GenericCompanion[CC] 61 | ): CanBuildFrom[Any, A, CC[A]] = { 62 | /* see https://github.com/scala/scala-collection-compat/issues/337 63 | `simpleCBF.apply` takes a by-name parameter and relies on 64 | repeated references generating new builders, thus this expression 65 | must be non-strict 66 | */ 67 | def builder: m.Builder[A, CC[A]] = fact match { 68 | case c.Seq | i.Seq => new IdentityPreservingBuilder[A, i.Seq](i.Seq.newBuilder[A]) 69 | case c.LinearSeq | i.LinearSeq => 70 | new IdentityPreservingBuilder[A, i.LinearSeq](i.LinearSeq.newBuilder[A]) 71 | case _ => fact.newBuilder[A] 72 | } 73 | simpleCBF(builder) 74 | } 75 | 76 | implicit def sortedSetCompanionToCBF[ 77 | A: Ordering, 78 | CC[X] <: c.SortedSet[X] with c.SortedSetLike[X, CC[X]] 79 | ](fact: SortedSetFactory[CC]): CanBuildFrom[Any, A, CC[A]] = 80 | simpleCBF(fact.newBuilder[A]) 81 | 82 | implicit def arrayCompanionToCBF[A: ClassTag](fact: Array.type): CanBuildFrom[Any, A, Array[A]] = 83 | simpleCBF(Array.newBuilder[A]) 84 | 85 | implicit def mapFactoryToCBF[K, V, CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]]( 86 | fact: MapFactory[CC] 87 | ): CanBuildFrom[Any, (K, V), CC[K, V]] = 88 | simpleCBF(fact.newBuilder[K, V]) 89 | 90 | implicit def sortedMapFactoryToCBF[ 91 | K: Ordering, 92 | V, 93 | CC[A, B] <: c.SortedMap[A, B] with c.SortedMapLike[A, B, CC[A, B]] 94 | ](fact: SortedMapFactory[CC]): CanBuildFrom[Any, (K, V), CC[K, V]] = 95 | simpleCBF(fact.newBuilder[K, V]) 96 | 97 | implicit def bitSetFactoryToCBF(fact: BitSetFactory[BitSet]): CanBuildFrom[Any, Int, BitSet] = 98 | simpleCBF(fact.newBuilder) 99 | 100 | implicit def immutableBitSetFactoryToCBF( 101 | fact: BitSetFactory[i.BitSet] 102 | ): CanBuildFrom[Any, Int, ImmutableBitSetCC[Int]] = 103 | simpleCBF(fact.newBuilder) 104 | 105 | implicit def mutableBitSetFactoryToCBF( 106 | fact: BitSetFactory[m.BitSet] 107 | ): CanBuildFrom[Any, Int, MutableBitSetCC[Int]] = 108 | simpleCBF(fact.newBuilder) 109 | 110 | implicit class IterableFactoryExtensionMethods[CC[X] <: GenTraversable[X]]( 111 | private val fact: GenericCompanion[CC] 112 | ) { 113 | def from[A](source: TraversableOnce[A]): CC[A] = 114 | fact.apply(source.toSeq: _*) 115 | } 116 | 117 | implicit class MapFactoryExtensionMethods[CC[A, B] <: Map[A, B] with MapLike[A, B, CC[A, B]]]( 118 | private val fact: MapFactory[CC] 119 | ) { 120 | def from[K, V](source: TraversableOnce[(K, V)]): CC[K, V] = 121 | fact.apply(source.toSeq: _*) 122 | } 123 | 124 | implicit class BitSetFactoryExtensionMethods[ 125 | C <: scala.collection.BitSet with scala.collection.BitSetLike[C] 126 | ](private val fact: BitSetFactory[C]) { 127 | def fromSpecific(source: TraversableOnce[Int]): C = 128 | fact.apply(source.toSeq: _*) 129 | } 130 | 131 | private[compat] def build[T, CC](builder: m.Builder[T, CC], source: TraversableOnce[T]): CC = { 132 | builder ++= source 133 | builder.result() 134 | } 135 | 136 | implicit def toImmutableSortedMapExtensions( 137 | fact: i.SortedMap.type 138 | ): ImmutableSortedMapExtensions = 139 | new ImmutableSortedMapExtensions(fact) 140 | 141 | implicit def toImmutableListMapExtensions(fact: i.ListMap.type): ImmutableListMapExtensions = 142 | new ImmutableListMapExtensions(fact) 143 | 144 | implicit def toImmutableHashMapExtensions(fact: i.HashMap.type): ImmutableHashMapExtensions = 145 | new ImmutableHashMapExtensions(fact) 146 | 147 | implicit def toImmutableTreeMapExtensions(fact: i.TreeMap.type): ImmutableTreeMapExtensions = 148 | new ImmutableTreeMapExtensions(fact) 149 | 150 | implicit def toImmutableIntMapExtensions(fact: i.IntMap.type): ImmutableIntMapExtensions = 151 | new ImmutableIntMapExtensions(fact) 152 | 153 | implicit def toImmutableLongMapExtensions(fact: i.LongMap.type): ImmutableLongMapExtensions = 154 | new ImmutableLongMapExtensions(fact) 155 | 156 | implicit def toMutableLongMapExtensions(fact: m.LongMap.type): MutableLongMapExtensions = 157 | new MutableLongMapExtensions(fact) 158 | 159 | implicit def toMutableHashMapExtensions(fact: m.HashMap.type): MutableHashMapExtensions = 160 | new MutableHashMapExtensions(fact) 161 | 162 | implicit def toMutableListMapExtensions(fact: m.ListMap.type): MutableListMapExtensions = 163 | new MutableListMapExtensions(fact) 164 | 165 | implicit def toMutableMapExtensions(fact: m.Map.type): MutableMapExtensions = 166 | new MutableMapExtensions(fact) 167 | 168 | implicit def toStreamExtensionMethods[A](stream: Stream[A]): StreamExtensionMethods[A] = 169 | new StreamExtensionMethods[A](stream) 170 | 171 | implicit def toSortedExtensionMethods[K, V <: Sorted[K, V]]( 172 | fact: Sorted[K, V] 173 | ): SortedExtensionMethods[K, V] = 174 | new SortedExtensionMethods[K, V](fact) 175 | 176 | implicit def toIteratorExtensionMethods[A](self: Iterator[A]): IteratorExtensionMethods[A] = 177 | new IteratorExtensionMethods[A](self) 178 | 179 | implicit def toTraversableExtensionMethods[A]( 180 | self: Traversable[A] 181 | ): TraversableExtensionMethods[A] = 182 | new TraversableExtensionMethods[A](self) 183 | 184 | implicit def toTraversableOnceExtensionMethods[A]( 185 | self: TraversableOnce[A] 186 | ): TraversableOnceExtensionMethods[A] = 187 | new TraversableOnceExtensionMethods[A](self) 188 | 189 | // This really belongs into scala.collection but there's already a package object 190 | // in scala-library so we can't add to it 191 | type IterableOnce[+X] = c.TraversableOnce[X] 192 | val IterableOnce = c.TraversableOnce 193 | 194 | implicit def toMapExtensionMethods[K, V]( 195 | self: scala.collection.Map[K, V] 196 | ): MapExtensionMethods[K, V] = 197 | new MapExtensionMethods[K, V](self) 198 | 199 | implicit def toMapViewExtensionMethods[K, V, C <: scala.collection.Map[K, V]]( 200 | self: IterableView[(K, V), C] 201 | ): MapViewExtensionMethods[K, V, C] = 202 | new MapViewExtensionMethods[K, V, C](self) 203 | } 204 | 205 | class ImmutableSortedMapExtensions(private val fact: i.SortedMap.type) extends AnyVal { 206 | def from[K: Ordering, V](source: TraversableOnce[(K, V)]): i.SortedMap[K, V] = 207 | build(i.SortedMap.newBuilder[K, V], source) 208 | } 209 | 210 | class ImmutableListMapExtensions(private val fact: i.ListMap.type) extends AnyVal { 211 | def from[K, V](source: TraversableOnce[(K, V)]): i.ListMap[K, V] = 212 | build(i.ListMap.newBuilder[K, V], source) 213 | } 214 | 215 | class ImmutableHashMapExtensions(private val fact: i.HashMap.type) extends AnyVal { 216 | def from[K, V](source: TraversableOnce[(K, V)]): i.HashMap[K, V] = 217 | build(i.HashMap.newBuilder[K, V], source) 218 | } 219 | 220 | class ImmutableTreeMapExtensions(private val fact: i.TreeMap.type) extends AnyVal { 221 | def from[K: Ordering, V](source: TraversableOnce[(K, V)]): i.TreeMap[K, V] = 222 | build(i.TreeMap.newBuilder[K, V], source) 223 | } 224 | 225 | class ImmutableIntMapExtensions(private val fact: i.IntMap.type) extends AnyVal { 226 | def from[V](source: TraversableOnce[(Int, V)]): i.IntMap[V] = 227 | build(i.IntMap.canBuildFrom[Int, V](), source) 228 | } 229 | 230 | class ImmutableLongMapExtensions(private val fact: i.LongMap.type) extends AnyVal { 231 | def from[V](source: TraversableOnce[(Long, V)]): i.LongMap[V] = 232 | build(i.LongMap.canBuildFrom[Long, V](), source) 233 | } 234 | 235 | class MutableLongMapExtensions(private val fact: m.LongMap.type) extends AnyVal { 236 | def from[V](source: TraversableOnce[(Long, V)]): m.LongMap[V] = 237 | build(m.LongMap.canBuildFrom[Long, V](), source) 238 | } 239 | 240 | class MutableHashMapExtensions(private val fact: m.HashMap.type) extends AnyVal { 241 | def from[K, V](source: TraversableOnce[(K, V)]): m.HashMap[K, V] = 242 | build(m.HashMap.canBuildFrom[K, V](), source) 243 | } 244 | 245 | class MutableListMapExtensions(private val fact: m.ListMap.type) extends AnyVal { 246 | def from[K, V](source: TraversableOnce[(K, V)]): m.ListMap[K, V] = 247 | build(m.ListMap.canBuildFrom[K, V](), source) 248 | } 249 | 250 | class MutableMapExtensions(private val fact: m.Map.type) extends AnyVal { 251 | def from[K, V](source: TraversableOnce[(K, V)]): m.Map[K, V] = 252 | build(m.Map.canBuildFrom[K, V](), source) 253 | } 254 | 255 | class StreamExtensionMethods[A](private val stream: Stream[A]) extends AnyVal { 256 | def lazyAppendedAll(as: => TraversableOnce[A]): Stream[A] = stream.append(as) 257 | } 258 | 259 | class SortedExtensionMethods[K, T <: Sorted[K, T]](private val fact: Sorted[K, T]) { 260 | def rangeFrom(from: K): T = fact.from(from) 261 | def rangeTo(to: K): T = fact.to(to) 262 | def rangeUntil(until: K): T = fact.until(until) 263 | } 264 | 265 | class IteratorExtensionMethods[A](private val self: c.Iterator[A]) extends AnyVal { 266 | def sameElements[B >: A](that: c.TraversableOnce[B]): Boolean = 267 | self.sameElements(that.iterator) 268 | def concat[B >: A](that: c.TraversableOnce[B]): c.TraversableOnce[B] = self ++ that 269 | def tapEach[U](f: A => U): c.Iterator[A] = self.map { a => f(a); a } 270 | } 271 | 272 | class TraversableOnceExtensionMethods[A](private val self: c.TraversableOnce[A]) extends AnyVal { 273 | def iterator: Iterator[A] = self.toIterator 274 | 275 | def minOption[B >: A](implicit ord: Ordering[B]): Option[A] = 276 | if (self.isEmpty) 277 | None 278 | else 279 | Some(self.min(ord)) 280 | 281 | def maxOption[B >: A](implicit ord: Ordering[B]): Option[A] = 282 | if (self.isEmpty) 283 | None 284 | else 285 | Some(self.max(ord)) 286 | 287 | def minByOption[B](f: A => B)(implicit cmp: Ordering[B]): Option[A] = 288 | if (self.isEmpty) 289 | None 290 | else 291 | Some(self.minBy(f)(cmp)) 292 | 293 | def maxByOption[B](f: A => B)(implicit cmp: Ordering[B]): Option[A] = 294 | if (self.isEmpty) 295 | None 296 | else 297 | Some(self.maxBy(f)(cmp)) 298 | } 299 | 300 | class TraversableExtensionMethods[A](private val self: c.Traversable[A]) extends AnyVal { 301 | def iterableFactory: GenericCompanion[Traversable] = self.companion 302 | 303 | def sizeCompare(otherSize: Int): Int = SizeCompareImpl.sizeCompareInt(self)(otherSize) 304 | def sizeIs: SizeCompareOps = new SizeCompareOps(self) 305 | def sizeCompare(that: c.Traversable[_]): Int = SizeCompareImpl.sizeCompareColl(self)(that) 306 | 307 | } 308 | 309 | class SeqExtensionMethods[A](private val self: c.Seq[A]) extends AnyVal { 310 | def lengthIs: SizeCompareOps = new SizeCompareOps(self) 311 | } 312 | 313 | class SizeCompareOps private[compat] (private val it: c.Traversable[_]) extends AnyVal { 314 | import SizeCompareImpl._ 315 | 316 | /** Tests if the size of the collection is less than some value. */ 317 | @inline def <(size: Int): Boolean = sizeCompareInt(it)(size) < 0 318 | 319 | /** Tests if the size of the collection is less than or equal to some value. */ 320 | @inline def <=(size: Int): Boolean = sizeCompareInt(it)(size) <= 0 321 | 322 | /** Tests if the size of the collection is equal to some value. */ 323 | @inline def ==(size: Int): Boolean = sizeCompareInt(it)(size) == 0 324 | 325 | /** Tests if the size of the collection is not equal to some value. */ 326 | @inline def !=(size: Int): Boolean = sizeCompareInt(it)(size) != 0 327 | 328 | /** Tests if the size of the collection is greater than or equal to some value. */ 329 | @inline def >=(size: Int): Boolean = sizeCompareInt(it)(size) >= 0 330 | 331 | /** Tests if the size of the collection is greater than some value. */ 332 | @inline def >(size: Int): Boolean = sizeCompareInt(it)(size) > 0 333 | } 334 | 335 | private object SizeCompareImpl { 336 | def sizeCompareInt(self: c.Traversable[_])(otherSize: Int): Int = 337 | self match { 338 | case self: c.SeqLike[_, _] => self.lengthCompare(otherSize) 339 | case _ => 340 | if (otherSize < 0) 1 341 | else { 342 | var i = 0 343 | val it = self.toIterator 344 | while (it.hasNext) { 345 | if (i == otherSize) return 1 346 | it.next() 347 | i += 1 348 | } 349 | i - otherSize 350 | } 351 | } 352 | 353 | // `IndexedSeq` is the only thing that we can safely say has a known size 354 | def sizeCompareColl(self: c.Traversable[_])(that: c.Traversable[_]): Int = 355 | that match { 356 | case that: c.IndexedSeq[_] => sizeCompareInt(self)(that.length) 357 | case _ => 358 | self match { 359 | case self: c.IndexedSeq[_] => 360 | val res = sizeCompareInt(that)(self.length) 361 | // can't just invert the result, because `-Int.MinValue == Int.MinValue` 362 | if (res == Int.MinValue) 1 else -res 363 | case _ => 364 | val thisIt = self.toIterator 365 | val thatIt = that.toIterator 366 | while (thisIt.hasNext && thatIt.hasNext) { 367 | thisIt.next() 368 | thatIt.next() 369 | } 370 | java.lang.Boolean.compare(thisIt.hasNext, thatIt.hasNext) 371 | } 372 | } 373 | } 374 | 375 | class TraversableLikeExtensionMethods[A, Repr](private val self: c.GenTraversableLike[A, Repr]) 376 | extends AnyVal { 377 | def tapEach[U](f: A => U)(implicit bf: CanBuildFrom[Repr, A, Repr]): Repr = 378 | self.map { a => f(a); a } 379 | 380 | def partitionMap[A1, A2, That, Repr1, Repr2](f: A => Either[A1, A2])(implicit 381 | bf1: CanBuildFrom[Repr, A1, Repr1], 382 | bf2: CanBuildFrom[Repr, A2, Repr2] 383 | ): (Repr1, Repr2) = { 384 | val l = bf1() 385 | val r = bf2() 386 | self.foreach { x => 387 | f(x) match { 388 | case Left(x1) => l += x1 389 | case Right(x2) => r += x2 390 | } 391 | } 392 | (l.result(), r.result()) 393 | } 394 | 395 | def groupMap[K, B, That]( 396 | key: A => K 397 | )(f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): Map[K, That] = { 398 | val map = m.Map.empty[K, m.Builder[B, That]] 399 | for (elem <- self) { 400 | val k = key(elem) 401 | val bldr = map.getOrElseUpdate(k, bf(self.repr)) 402 | bldr += f(elem) 403 | } 404 | val res = Map.newBuilder[K, That] 405 | for ((k, bldr) <- map) res += ((k, bldr.result())) 406 | res.result() 407 | } 408 | 409 | def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): Map[K, B] = { 410 | val map = m.Map.empty[K, B] 411 | for (elem <- self) { 412 | val k = key(elem) 413 | val v = map.get(k) match { 414 | case Some(b) => reduce(b, f(elem)) 415 | case None => f(elem) 416 | } 417 | map.put(k, v) 418 | } 419 | map.toMap 420 | } 421 | } 422 | 423 | class TrulyTraversableLikeExtensionMethods[El1, Repr1]( 424 | private val self: TraversableLike[El1, Repr1] 425 | ) extends AnyVal { 426 | 427 | def lazyZip[El2, Repr2, T2](t2: T2)(implicit 428 | w2: T2 => IterableLike[El2, Repr2] 429 | ): Tuple2Zipped[El1, Repr1, El2, Repr2] = new Tuple2Zipped((self, t2)) 430 | } 431 | 432 | class Tuple2ZippedExtensionMethods[El1, Repr1, El2, Repr2]( 433 | private val self: Tuple2Zipped[El1, Repr1, El2, Repr2] 434 | ) { 435 | 436 | def lazyZip[El3, Repr3, T3](t3: T3)(implicit 437 | w3: T3 => IterableLike[El3, Repr3] 438 | ): Tuple3Zipped[El1, Repr1, El2, Repr2, El3, Repr3] = 439 | new Tuple3Zipped((self.colls._1, self.colls._2, t3)) 440 | } 441 | 442 | class MapExtensionMethods[K, V](private val self: scala.collection.Map[K, V]) extends AnyVal { 443 | 444 | def foreachEntry[U](f: (K, V) => U): Unit = 445 | self.foreach { case (k, v) => f(k, v) } 446 | 447 | } 448 | 449 | class MapViewExtensionMethods[K, V, C <: scala.collection.Map[K, V]]( 450 | private val self: IterableView[(K, V), C] 451 | ) extends AnyVal { 452 | 453 | def mapValues[W, That](f: V => W)(implicit 454 | bf: CanBuildFrom[IterableView[(K, V), C], (K, W), That] 455 | ): That = 456 | self.map[(K, W), That] { case (k, v) => (k, f(v)) } 457 | 458 | // TODO: Replace the current implementation of `mapValues` with this 459 | // after major version bump when bincompat can be broken. 460 | // At the same time, remove `canBuildFromIterableViewMapLike` 461 | /* 462 | def mapValues[W](f: V => W): IterableView[(K, W), C] = 463 | // the implementation of `self.map` also casts the result 464 | self.map({ case (k, v) => (k, f(v)) }).asInstanceOf[IterableView[(K, W), C]] 465 | */ 466 | 467 | def filterKeys(p: K => Boolean): IterableView[(K, V), C] = 468 | self.filter { case (k, _) => p(k) } 469 | } 470 | 471 | class ImmutableQueueExtensionMethods[A](private val self: i.Queue[A]) extends AnyVal { 472 | def enqueueAll[B >: A](iter: c.Iterable[B]): i.Queue[B] = 473 | self.enqueue(iter.to[i.Iterable]) 474 | } 475 | 476 | class MutableQueueExtensionMethods[Element](private val self: m.Queue[Element]) extends AnyVal { 477 | def enqueueAll(iter: c.Iterable[Element]): Unit = 478 | self.enqueue(iter.toIndexedSeq: _*) 479 | } 480 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.12/scala/collection/compat/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.collection 14 | 15 | import scala.collection.generic.{ CanBuildFrom, GenericOrderedCompanion, IsTraversableLike } 16 | import scala.{ collection => c } 17 | import scala.runtime.Tuple2Zipped 18 | import scala.collection.{ immutable => i, mutable => m } 19 | 20 | package object compat extends compat.PackageShared { 21 | implicit class MutableTreeMapExtensions2(private val fact: m.TreeMap.type) extends AnyVal { 22 | def from[K: Ordering, V](source: TraversableOnce[(K, V)]): m.TreeMap[K, V] = 23 | build(m.TreeMap.newBuilder[K, V], source) 24 | } 25 | 26 | implicit class MutableSortedMapExtensions(private val fact: m.SortedMap.type) extends AnyVal { 27 | def from[K: Ordering, V](source: TraversableOnce[(K, V)]): m.SortedMap[K, V] = 28 | build(m.SortedMap.newBuilder[K, V], source) 29 | } 30 | 31 | implicit def genericOrderedCompanionToCBF[A, CC[X] <: Traversable[X]]( 32 | fact: GenericOrderedCompanion[CC] 33 | )(implicit ordering: Ordering[A]): CanBuildFrom[Any, A, CC[A]] = 34 | CompatImpl.simpleCBF(fact.newBuilder[A]) 35 | 36 | // CanBuildFrom instances for `IterableView[(K, V), Map[K, V]]` that preserve 37 | // the strict type of the view to be `Map` instead of `Iterable` 38 | // Instances produced by this method are used to chain `filterKeys` after `mapValues` 39 | implicit def canBuildFromIterableViewMapLike[K, V, L, W, CC[X, Y] <: Map[X, Y]] 40 | : CanBuildFrom[IterableView[(K, V), CC[K, V]], (L, W), IterableView[(L, W), CC[L, W]]] = 41 | new CanBuildFrom[IterableView[(K, V), CC[K, V]], (L, W), IterableView[(L, W), CC[L, W]]] { 42 | // `CanBuildFrom` parameters are used as type constraints, they are not used 43 | // at run-time, hence the dummy builder implementations 44 | def apply(from: IterableView[(K, V), CC[K, V]]) = new TraversableView.NoBuilder 45 | def apply() = new TraversableView.NoBuilder 46 | } 47 | 48 | implicit def toTraversableLikeExtensionMethods[Repr](self: Repr)(implicit 49 | traversable: IsTraversableLike[Repr] 50 | ): TraversableLikeExtensionMethods[traversable.A, Repr] = 51 | new TraversableLikeExtensionMethods[traversable.A, Repr](traversable.conversion(self)) 52 | 53 | implicit def toSeqExtensionMethods[A](self: c.Seq[A]): SeqExtensionMethods[A] = 54 | new SeqExtensionMethods[A](self) 55 | 56 | implicit def toTrulyTraversableLikeExtensionMethods[T1, El1, Repr1](self: T1)(implicit 57 | w1: T1 => TraversableLike[El1, Repr1] 58 | ): TrulyTraversableLikeExtensionMethods[El1, Repr1] = 59 | new TrulyTraversableLikeExtensionMethods[El1, Repr1](w1(self)) 60 | 61 | implicit def toTuple2ZippedExtensionMethods[El1, Repr1, El2, Repr2]( 62 | self: Tuple2Zipped[El1, Repr1, El2, Repr2] 63 | ): Tuple2ZippedExtensionMethods[El1, Repr1, El2, Repr2] = 64 | new Tuple2ZippedExtensionMethods[El1, Repr1, El2, Repr2](self) 65 | 66 | implicit def toImmutableQueueExtensionMethods[A]( 67 | self: i.Queue[A] 68 | ): ImmutableQueueExtensionMethods[A] = 69 | new ImmutableQueueExtensionMethods[A](self) 70 | 71 | implicit def toMutableQueueExtensionMethods[A]( 72 | self: m.Queue[A] 73 | ): MutableQueueExtensionMethods[A] = 74 | new MutableQueueExtensionMethods[A](self) 75 | } 76 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.12/scala/jdk/CollectionConverters.scala: -------------------------------------------------------------------------------- 1 | package scala.jdk 2 | 3 | import scala.collection.convert.{ DecorateAsJava, DecorateAsScala } 4 | 5 | object CollectionConverters extends DecorateAsJava with DecorateAsScala 6 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.13/scala/collection/compat/immutable/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.collection.compat 14 | 15 | package object immutable { 16 | type ArraySeq[+T] = scala.collection.immutable.ArraySeq[T] 17 | val ArraySeq = scala.collection.immutable.ArraySeq 18 | 19 | type LazyList[+T] = scala.collection.immutable.LazyList[T] 20 | val LazyList = scala.collection.immutable.LazyList 21 | } 22 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.13/scala/collection/compat/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Scala (https://www.scala-lang.org) 3 | * 4 | * Copyright EPFL and Lightbend, Inc. 5 | * 6 | * Licensed under Apache License 2.0 7 | * (http://www.apache.org/licenses/LICENSE-2.0). 8 | * 9 | * See the NOTICE file distributed with this work for 10 | * additional information regarding copyright ownership. 11 | */ 12 | 13 | package scala.collection 14 | 15 | package object compat { 16 | type Factory[-A, +C] = scala.collection.Factory[A, C] 17 | val Factory = scala.collection.Factory 18 | 19 | type BuildFrom[-From, -A, +C] = scala.collection.BuildFrom[From, A, C] 20 | val BuildFrom = scala.collection.BuildFrom 21 | 22 | type IterableOnce[+X] = scala.collection.IterableOnce[X] 23 | val IterableOnce = scala.collection.IterableOnce 24 | } 25 | -------------------------------------------------------------------------------- /core/shared/src/main/scala-2.13/scala/util/control/compat/package.scala: -------------------------------------------------------------------------------- 1 | package scala.util.control 2 | 3 | package object compat { 4 | type ControlThrowable = scala.util.control.ControlThrowable 5 | } 6 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/AttributedCharacterIterator.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | trait AttributedCharacterIterator extends CharacterIterator { 4 | def getRunStart(): Int 5 | 6 | def getRunStart(attribute: AttributedCharacterIterator.Attribute): Int 7 | 8 | def getRunStart(attributes: java.util.Set[_ <: AttributedCharacterIterator.Attribute]): Int 9 | 10 | def getRunLimit(): Int 11 | 12 | def getRunLimit(attribute: AttributedCharacterIterator.Attribute): Int 13 | 14 | def getRunLimit(attributes: java.util.Set[_ <: AttributedCharacterIterator.Attribute]): Int 15 | 16 | def getAttributes(): java.util.Map[AttributedCharacterIterator.Attribute, AnyRef] 17 | 18 | def getAttribute(attribute: AttributedCharacterIterator.Attribute): AnyRef 19 | 20 | def getAllAttributeKeys(): java.util.Set[AttributedCharacterIterator.Attribute] 21 | } 22 | 23 | object AttributedCharacterIterator { 24 | class Attribute protected (private[this] val name: String) { 25 | override final def equals(that: Any): Boolean = 26 | that match { 27 | case t: Attribute => this.eq(t) // As per javadocs 28 | case _ => false 29 | } 30 | 31 | override final def hashCode(): Int = name.hashCode 32 | 33 | override def toString: String = 34 | s"java.text.AttributedCharacterIterator$$Attribute(${getName()})" 35 | 36 | protected def getName(): String = name 37 | } 38 | 39 | object Attribute { 40 | val LANGUAGE: Attribute = new Attribute("language") 41 | val READING: Attribute = new Attribute("reading") 42 | val INPUT_METHOD_SEGMENT: Attribute = new Attribute("input_method_segment") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/CharacterIterator.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | trait CharacterIterator extends Cloneable { 4 | def first(): Char 5 | 6 | def last(): Char 7 | 8 | def current(): Char 9 | 10 | def next(): Char 11 | 12 | def previous(): Char 13 | 14 | def setIndex(position: Int): Char 15 | 16 | def getBeginIndex(): Int 17 | 18 | def getEndIndex(): Int 19 | 20 | def getIndex(): Int 21 | } 22 | 23 | object CharacterIterator { 24 | val DONE: Char = '\uFFFF' 25 | } 26 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/DateFormat.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import java.util.Locale 4 | 5 | import locales.LocalesDb 6 | import locales.cldr.{ CalendarPatterns, LDML } 7 | 8 | abstract class DateFormat protected () extends Format 9 | // override final def format(obj: AnyRef, toAppendTo: StringBuffer, pos: FieldPosition): StringBuffer 10 | // def format(date: Date, toAppendTo: StringBuffer, pos: FieldPosition): StringBuffer 11 | 12 | // TODO implement 13 | // final def format(date: Date): String 14 | // def parse(source: String, parsePosition: ParsePosition): Date 15 | // def parse(source: String): Date 16 | // override final def parseObject(source: String, pos: ParsePosition): AnyRef 17 | // def setCalendar(newCalendar: Calendar): Unit 18 | // def getCalendar(): Calendar 19 | // def setNumberFormat(newNumberFormat: NumberFormat): Unit 20 | // def getNumberFormat(): NumberFormat 21 | // def setTimeZone(zone: TimeZone): Unit 22 | // def getTimeZone(): TimeZone 23 | // def setLenient(lenient: Boolean): Unit 24 | // def isLenient(): Boolean 25 | // override def hashCode(): Int 26 | // override def equals(obj: Any): Boolean 27 | // override def clone(): Any 28 | 29 | object DateFormat { 30 | val ERA_FIELD: Int = 0 31 | val YEAR_FIELD: Int = 1 32 | val MONTH_FIELD: Int = 2 33 | val DATE_FIELD: Int = 3 34 | val HOUR_OF_DAY1_FIELD: Int = 4 35 | val HOUR_OF_DAY0_FIELD: Int = 5 36 | val MINUTE_FIELD: Int = 6 37 | val SECOND_FIELD: Int = 7 38 | val MILLISECOND_FIELD: Int = 8 39 | val DAY_OF_WEEK_FIELD: Int = 9 40 | val DAY_OF_YEAR_FIELD: Int = 10 41 | val DAY_OF_WEEK_IN_MONTH_FIELD: Int = 11 42 | val WEEK_OF_YEAR_FIELD: Int = 12 43 | val WEEK_OF_MONTH_FIELD: Int = 13 44 | val AM_PM_FIELD: Int = 14 45 | val HOUR1_FIELD: Int = 15 46 | val HOUR0_FIELD: Int = 16 47 | val TIMEZONE_FIELD: Int = 17 48 | 49 | val FULL: Int = 0 50 | val LONG: Int = 1 51 | val MEDIUM: Int = 2 52 | val SHORT: Int = 3 53 | val DEFAULT: Int = 2 54 | 55 | private def patternsR(ldml: LDML, get: CalendarPatterns => Option[String]): Option[String] = 56 | ldml.calendarPatterns.flatMap(get).orElse(ldml.parent.flatMap(patternsR(_, get))) 57 | 58 | final def getTimeInstance(): DateFormat = getTimeInstance(DEFAULT) 59 | 60 | final def getTimeInstance(style: Int): DateFormat = 61 | getTimeInstance(style, Locale.getDefault(Locale.Category.FORMAT)) 62 | 63 | final def getTimeInstance(style: Int, aLocale: Locale): DateFormat = 64 | LocalesDb 65 | .ldml(aLocale) 66 | .flatMap { ldml => 67 | val ptrn = patternsR(ldml, _.timePatterns.get(style)) 68 | ptrn.map(new SimpleDateFormat(_, aLocale)) 69 | } 70 | .getOrElse(new SimpleDateFormat("", aLocale)) 71 | 72 | final def getDateInstance(): DateFormat = getDateInstance(DEFAULT) 73 | 74 | final def getDateInstance(style: Int): DateFormat = 75 | getDateInstance(style, Locale.getDefault(Locale.Category.FORMAT)) 76 | 77 | final def getDateInstance(style: Int, aLocale: Locale): DateFormat = 78 | LocalesDb 79 | .ldml(aLocale) 80 | .flatMap { ldml => 81 | val ptrn = patternsR(ldml, _.datePatterns.get(style)) 82 | ptrn.map(new SimpleDateFormat(_, aLocale)) 83 | } 84 | .getOrElse(new SimpleDateFormat("", aLocale)) 85 | 86 | final def getDateTimeInstance(): DateFormat = getDateInstance(DEFAULT) 87 | 88 | final def getDateTimeInstance(dateStyle: Int, timeStyle: Int): DateFormat = 89 | getDateTimeInstance(dateStyle, timeStyle, Locale.getDefault(Locale.Category.FORMAT)) 90 | 91 | final def getDateTimeInstance(dateStyle: Int, timeStyle: Int, aLocale: Locale): DateFormat = 92 | LocalesDb 93 | .ldml(aLocale) 94 | .flatMap { ldml => 95 | val datePtrn = patternsR(ldml, _.datePatterns.get(dateStyle)) 96 | val timePtrn = patternsR(ldml, _.timePatterns.get(timeStyle)) 97 | (datePtrn, timePtrn) match { 98 | case (Some(d), Some(t)) => Some(new SimpleDateFormat(s"$d $t", aLocale)) 99 | case (Some(d), None) => Some(new SimpleDateFormat(s"$d", aLocale)) 100 | case (None, Some(t)) => Some(new SimpleDateFormat(s"$t", aLocale)) 101 | case _ => Some(new SimpleDateFormat("", aLocale)) 102 | } 103 | } 104 | .getOrElse(new SimpleDateFormat("", aLocale)) 105 | 106 | final def getInstance(): DateFormat = 107 | getDateTimeInstance(SHORT, SHORT) 108 | 109 | def getAvailableLocales(): Array[Locale] = Locale.getAvailableLocales() 110 | } 111 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/DateFormatSymbols.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import java.util.{ Arrays, Locale } 4 | import locales.LocalesDb 5 | import locales.cldr.{ CalendarSymbols, LDML } 6 | 7 | object DateFormatSymbols { 8 | 9 | def getAvailableLocales(): Array[Locale] = Locale.getAvailableLocales() 10 | 11 | def getInstance(): DateFormatSymbols = 12 | getInstance(Locale.getDefault(Locale.Category.FORMAT)) 13 | 14 | def getInstance(locale: Locale): DateFormatSymbols = 15 | initialize(locale, new DateFormatSymbols(locale)) 16 | 17 | private def initialize(locale: Locale, dfs: DateFormatSymbols): DateFormatSymbols = 18 | LocalesDb 19 | .ldml(locale) 20 | .map(l => toDFS(locale, dfs, l)) 21 | .getOrElse(dfs) 22 | 23 | private def copyAndPad(m: List[String], size: Int, v: String): Array[String] = { 24 | val p = Arrays.copyOf[String](m.toArray[String], size) 25 | (m.length until size).foreach(p(_) = v) 26 | p 27 | } 28 | 29 | private def padAndCopyDays(m: List[String], size: Int, v: String): Array[String] = { 30 | // Days in the JVM are stored starting on index 1, and 0 is empty 31 | val v = new Array[String](size) 32 | (0 until size).foreach(i => if (i == 0 || i > m.length) v(i) = "" else v(i) = m(i - 1)) 33 | v 34 | } 35 | 36 | private def toDFS(locale: Locale, dfs: DateFormatSymbols, ldml: LDML): DateFormatSymbols = { 37 | def parentSymbols(ldml: LDML): Option[CalendarSymbols] = 38 | ldml.calendarSymbols.orElse(ldml.parent.flatMap(parentSymbols)) 39 | 40 | def elementsArray( 41 | ldml: LDML, 42 | read: CalendarSymbols => Option[List[String]] 43 | ): Option[List[String]] = 44 | parentSymbols(ldml).flatMap { s => 45 | read(s).orElse(ldml.parent.flatMap(elementsArray(_, read))) 46 | } 47 | 48 | def setElements( 49 | ldml: LDML, 50 | read: CalendarSymbols => List[String], 51 | set: List[String] => Unit 52 | ): Unit = { 53 | def readNonEmpty(c: CalendarSymbols) = 54 | read(c) match { 55 | case Nil => None 56 | case x => Some(x) 57 | } 58 | elementsArray(ldml, readNonEmpty).orElse(Some(Nil)).foreach(set) 59 | } 60 | 61 | setElements(ldml, _.months, l => dfs.setMonths(copyAndPad(l, 13, ""))) 62 | setElements(ldml, _.shortMonths, l => dfs.setShortMonths(copyAndPad(l, 13, ""))) 63 | setElements(ldml, _.weekdays, l => dfs.setWeekdays(padAndCopyDays(l, 8, ""))) 64 | setElements(ldml, _.shortWeekdays, l => dfs.setShortWeekdays(padAndCopyDays(l, 8, ""))) 65 | setElements(ldml, _.amPm, l => dfs.setAmPmStrings(copyAndPad(l, 2, ""))) 66 | setElements(ldml, _.eras, l => dfs.setEras(copyAndPad(l, 2, ""))) 67 | dfs 68 | } 69 | } 70 | 71 | class DateFormatSymbols(private[this] val locale: Locale) extends Cloneable { 72 | private[this] var eras: Array[String] = Array() 73 | private[this] var months: Array[String] = Array() 74 | private[this] var shortMonths: Array[String] = Array() 75 | private[this] var weekdays: Array[String] = Array() 76 | private[this] var shortWeekdays: Array[String] = Array() 77 | private[this] var amPmStrings: Array[String] = Array() 78 | private[this] var zoneStrings: Array[Array[String]] = Array() 79 | private[this] var localPatternChars: String = "" 80 | 81 | DateFormatSymbols.initialize(locale, this) 82 | 83 | def this() = this(Locale.getDefault(Locale.Category.FORMAT)) 84 | 85 | def getEras(): Array[String] = eras 86 | 87 | def setEras(eras: Array[String]): Unit = { 88 | if (eras == null) throw new NullPointerException() 89 | this.eras = Array[String](eras: _*) 90 | } 91 | 92 | def getMonths(): Array[String] = months 93 | 94 | def setMonths(months: Array[String]): Unit = { 95 | if (months == null) throw new NullPointerException() 96 | this.months = Array[String](months: _*) 97 | } 98 | 99 | def getShortMonths(): Array[String] = shortMonths 100 | 101 | def setShortMonths(shortMonths: Array[String]): Unit = { 102 | if (shortMonths == null) throw new NullPointerException() 103 | this.shortMonths = Array[String](shortMonths: _*) 104 | } 105 | 106 | def getWeekdays(): Array[String] = weekdays 107 | 108 | def setWeekdays(weekdays: Array[String]): Unit = { 109 | if (weekdays == null) throw new NullPointerException() 110 | this.weekdays = Array[String](weekdays: _*) 111 | } 112 | 113 | def getShortWeekdays(): Array[String] = shortWeekdays 114 | 115 | def setShortWeekdays(shortWeekdays: Array[String]): Unit = { 116 | if (shortWeekdays == null) throw new NullPointerException() 117 | this.shortWeekdays = Array[String](shortWeekdays: _*) 118 | } 119 | 120 | def getAmPmStrings(): Array[String] = amPmStrings 121 | 122 | def setAmPmStrings(amPmStrings: Array[String]): Unit = { 123 | if (amPmStrings == null) throw new NullPointerException() 124 | this.amPmStrings = Array[String](amPmStrings: _*) 125 | } 126 | 127 | def getZoneStrings(): Array[Array[String]] = zoneStrings 128 | 129 | def setZoneStrings(zoneStrings: Array[Array[String]]): Unit = { 130 | if (zoneStrings == null) throw new NullPointerException() 131 | if (zoneStrings.exists(_.length < 5)) 132 | throw new IllegalArgumentException() 133 | val copy = zoneStrings.map(Array[String](_: _*)) 134 | this.zoneStrings = Array[Array[String]](copy: _*) 135 | } 136 | 137 | def getLocalPatternChars(): String = localPatternChars 138 | 139 | def setLocalPatternChars(localPatternChars: String): Unit = { 140 | if (localPatternChars == null) throw new NullPointerException() 141 | this.localPatternChars = localPatternChars 142 | } 143 | 144 | override def clone(): AnyRef = { 145 | val dfs = new DateFormatSymbols() 146 | dfs.setEras(getEras()) 147 | dfs.setMonths(getMonths()) 148 | dfs.setShortMonths(getShortMonths()) 149 | dfs.setWeekdays(getWeekdays()) 150 | dfs.setShortWeekdays(getShortWeekdays()) 151 | dfs.setAmPmStrings(getAmPmStrings()) 152 | dfs.setZoneStrings(getZoneStrings()) 153 | dfs.setLocalPatternChars(getLocalPatternChars()) 154 | dfs 155 | } 156 | 157 | override def hashCode(): Int = { 158 | val state = Seq( 159 | Array[AnyRef](eras: _*), 160 | Array[AnyRef](months: _*), 161 | Array[AnyRef](shortMonths: _*), 162 | Array[AnyRef](weekdays: _*), 163 | Array[AnyRef](shortWeekdays: _*), 164 | Array[AnyRef](amPmStrings: _*) 165 | ) 166 | val s = state.map(Arrays.hashCode).foldLeft(0)((a, b) => 31 * a + b) 167 | val zs = zoneStrings.map(Array[AnyRef](_: _*)) 168 | val zsc = Array[AnyRef](zs: _*) 169 | 170 | 31 * (31 * s + Arrays.hashCode(zsc)) + localPatternChars.hashCode 171 | } 172 | 173 | override def equals(other: Any): Boolean = 174 | other match { 175 | case that: DateFormatSymbols => 176 | eras.sameElements(that.getEras()) && 177 | months.sameElements(that.getMonths()) && 178 | shortMonths.sameElements(that.getShortMonths()) && 179 | weekdays.sameElements(that.getWeekdays()) && 180 | shortWeekdays.sameElements(that.getShortWeekdays()) && 181 | amPmStrings.sameElements(that.getAmPmStrings()) && 182 | zoneStrings.sameElements(that.getZoneStrings()) && 183 | localPatternChars == that.getLocalPatternChars() 184 | case _ => false 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/DecimalFormatSymbols.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import java.util.Locale 4 | import locales.cldr.{ LDML, NumberingSystem, Symbols } 5 | import locales.LocalesDb 6 | 7 | object DecimalFormatSymbols { 8 | 9 | def getAvailableLocales(): Array[Locale] = Locale.getAvailableLocales() 10 | 11 | def getInstance(): DecimalFormatSymbols = 12 | getInstance(Locale.getDefault(Locale.Category.FORMAT)) 13 | 14 | def getInstance(locale: Locale): DecimalFormatSymbols = 15 | initialize(locale, new DecimalFormatSymbols(locale)) 16 | 17 | private def initialize(locale: Locale, dfs: DecimalFormatSymbols): DecimalFormatSymbols = { 18 | // Find the correct numbering systems for the ldml 19 | def ns(ldml: LDML): NumberingSystem = 20 | ldml.defaultNS 21 | .flatMap { n => 22 | LocalesDb.root.digitSymbols.find(_.ns == n).collect { 23 | case Symbols(_, Some(alias), _, _, _, _, _, _, _, _, _) => alias 24 | case _ => n 25 | } 26 | } 27 | .getOrElse(LocalesDb.latn) 28 | 29 | LocalesDb 30 | .ldml(locale) 31 | .map(l => toDFS(dfs, l, ns(l))) 32 | .getOrElse(dfs) 33 | } 34 | 35 | private def toDFS( 36 | dfs: DecimalFormatSymbols, 37 | ldml: LDML, 38 | ns: NumberingSystem 39 | ): DecimalFormatSymbols = { 40 | 41 | def parentSymbols(ldml: LDML, ns: NumberingSystem): Option[Symbols] = 42 | ldml.digitSymbols 43 | .find(_.ns == ns) 44 | .orElse(ldml.parent.flatMap(parentSymbols(_, ns))) 45 | 46 | def parentSymbolR[A]( 47 | ldml: LDML, 48 | ns: NumberingSystem, 49 | contains: Symbols => Option[A] 50 | ): Option[A] = 51 | parentSymbols(ldml, ns).flatMap { 52 | case Symbols(_, Some(alias), _, _, _, _, _, _, _, _, _) => 53 | parentSymbolR(ldml, alias, contains) 54 | 55 | case s @ Symbols(_, _, _, _, _, _, _, _, _, _, _) => 56 | contains(s) 57 | .orElse(ldml.parent.flatMap(parentSymbolR(_, ns, contains))) 58 | } 59 | 60 | def setSymbol[A]( 61 | ldml: LDML, 62 | ns: NumberingSystem, 63 | contains: Symbols => Option[A], 64 | set: A => Unit 65 | ): Unit = 66 | parentSymbolR(ldml, ns, contains).foreach(set) 67 | 68 | // Read the zero from the default numeric system 69 | ns.digits.headOption.foreach(dfs.setZeroDigit) 70 | // Set the components of the decimal format symbol 71 | setSymbol(ldml, ns, _.decimal, dfs.setDecimalSeparator) 72 | setSymbol(ldml, ns, _.group, dfs.setGroupingSeparator) 73 | setSymbol(ldml, ns, _.list, dfs.setPatternSeparator) 74 | setSymbol(ldml, ns, _.percent, dfs.setPercent) 75 | setSymbol(ldml, ns, _.minus, dfs.setMinusSign) 76 | setSymbol(ldml, ns, _.perMille, dfs.setPerMill) 77 | setSymbol(ldml, ns, _.infinity, dfs.setInfinity) 78 | setSymbol(ldml, ns, _.nan, dfs.setNaN) 79 | setSymbol(ldml, ns, _.exp, dfs.setExponentSeparator) 80 | // CLDR fixes the pattern character 81 | // http://www.unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns 82 | dfs.setDigit('#') 83 | dfs 84 | } 85 | } 86 | 87 | class DecimalFormatSymbols(private[this] val locale: Locale) extends Cloneable { 88 | private[this] var zeroDigit: Char = 0 89 | private[this] var minusSign: Char = 0 90 | private[this] var decimalSeparator: Char = 0 91 | private[this] var groupingSeparator: Char = 0 92 | private[this] var perMill: Char = 0 93 | private[this] var percent: Char = 0 94 | private[this] var digit: Char = 0 95 | private[this] var patternSeparator: Char = 0 96 | private[this] var infinity: String = null 97 | private[this] var nan: String = null 98 | private[this] var exp: String = null 99 | 100 | DecimalFormatSymbols.initialize(locale, this) 101 | 102 | def this() = this(Locale.getDefault(Locale.Category.FORMAT)) 103 | 104 | def getZeroDigit(): Char = zeroDigit 105 | 106 | def setZeroDigit(zeroDigit: Char): Unit = this.zeroDigit = zeroDigit 107 | 108 | def getGroupingSeparator(): Char = groupingSeparator 109 | 110 | def setGroupingSeparator(groupingSeparator: Char): Unit = 111 | this.groupingSeparator = groupingSeparator 112 | 113 | def getDecimalSeparator(): Char = decimalSeparator 114 | 115 | def setDecimalSeparator(decimalSeparator: Char): Unit = 116 | this.decimalSeparator = decimalSeparator 117 | 118 | def getPerMill(): Char = perMill 119 | 120 | def setPerMill(perMill: Char): Unit = this.perMill = perMill 121 | 122 | def getPercent(): Char = percent 123 | 124 | def setPercent(percent: Char): Unit = this.percent = percent 125 | 126 | def getDigit(): Char = digit 127 | 128 | def setDigit(digit: Char): Unit = this.digit = digit 129 | 130 | def getPatternSeparator(): Char = patternSeparator 131 | 132 | def setPatternSeparator(patternSeparator: Char): Unit = 133 | this.patternSeparator = patternSeparator 134 | 135 | def getInfinity(): String = infinity 136 | 137 | def setInfinity(infinity: String): Unit = this.infinity = infinity 138 | 139 | def getNaN(): String = nan 140 | 141 | def setNaN(nan: String): Unit = this.nan = nan 142 | 143 | def getMinusSign(): Char = minusSign 144 | 145 | def setMinusSign(minusSign: Char): Unit = this.minusSign = minusSign 146 | 147 | // TODO Implement currency methods 148 | // def getCurrencySymbol(): String 149 | 150 | // def setCurrencySymbol(currency: String): Unit 151 | 152 | // def getInternationalCurrencySymbol(): String 153 | 154 | // def setInternationalCurrencySymbol(currency: String): Unit 155 | 156 | // def getCurrency(): Currency 157 | 158 | // def setCurrency(currency: Currency): Unit 159 | 160 | // def getMonetaryDecimalSeparator(): Char 161 | 162 | // def setMonetaryDecimalSeparator(sep: Char): Unit 163 | 164 | def getExponentSeparator(): String = if (exp != null) exp else "" 165 | 166 | def setExponentSeparator(sep: String): Unit = { 167 | if (sep == null) 168 | throw new NullPointerException() 169 | this.exp = sep 170 | } 171 | 172 | override def clone(): AnyRef = 173 | new DecimalFormatSymbols(locale) 174 | 175 | override def equals(obj: Any): Boolean = 176 | obj match { 177 | case d: DecimalFormatSymbols => 178 | d.getZeroDigit() == getZeroDigit() && 179 | d.getGroupingSeparator() == getGroupingSeparator() && 180 | d.getDecimalSeparator() == getDecimalSeparator() && 181 | d.getPerMill() == getPerMill() && 182 | d.getPercent() == getPercent() && 183 | d.getDigit() == getDigit() && 184 | d.getPatternSeparator() == getPatternSeparator() && 185 | d.getInfinity() == getInfinity() && 186 | d.getNaN() == getNaN() && 187 | d.getMinusSign() == getMinusSign() && 188 | d.getExponentSeparator() == getExponentSeparator() 189 | 190 | case _ => false 191 | } 192 | 193 | // Oddly the JVM seems to always return the same value 194 | // it breaks the hashCode contract 195 | override def hashCode(): Int = { 196 | val prime = 31 197 | var result = 1 198 | result = prime * result + getZeroDigit().hashCode() 199 | result = prime * result + getGroupingSeparator().hashCode() 200 | result = prime * result + getDecimalSeparator().hashCode() 201 | result = prime * result + getPerMill().hashCode() 202 | result = prime * result + getPercent().hashCode() 203 | result = prime * result + getDigit().hashCode() 204 | result = prime * result + getPatternSeparator().hashCode() 205 | result = prime * result + (if (getInfinity() != null) getInfinity().## 206 | else 0) 207 | result = prime * result + (if (getNaN() != null) getNaN().hashCode() else 0) 208 | result = prime * result + getMinusSign().hashCode() 209 | result = prime * result + getExponentSeparator().hashCode() 210 | result 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/FieldPosition.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | // Make this a super class so we can match against it 4 | private[text] object IgnoreFieldPosition extends FieldPosition(0) 5 | 6 | class FieldPosition(private[this] val attribute: Format.Field, private[this] val fieldId: Int) { 7 | private[this] var beginIndex: Int = 0 8 | private[this] var endIndex: Int = 0 9 | 10 | def this(attribute: Format.Field) = this(attribute, -1) 11 | 12 | def this(fieldId: Int) = this(null, fieldId) 13 | 14 | def getFieldAttribute(): Format.Field = attribute 15 | 16 | def getField(): Int = fieldId 17 | 18 | def getBeginIndex(): Int = beginIndex 19 | 20 | def getEndIndex(): Int = endIndex 21 | 22 | def setBeginIndex(bi: Int): Unit = beginIndex = bi 23 | 24 | def setEndIndex(ei: Int): Unit = endIndex = ei 25 | 26 | override def equals(other: Any): Boolean = 27 | other match { 28 | case that: FieldPosition => 29 | getBeginIndex() == that.getBeginIndex() && 30 | getEndIndex() == that.getEndIndex() && 31 | getFieldAttribute() == that.getFieldAttribute() && 32 | getField() == that.getField() 33 | case _ => false 34 | } 35 | 36 | override def hashCode(): Int = { 37 | // NOTE, the JVM doesn't use field attribute on hash but it uses on equal 38 | val state = Seq(getBeginIndex(), getEndIndex(), /* getFieldAttribute,*/ getField()) 39 | state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) 40 | } 41 | 42 | override def toString(): String = 43 | s"java.text.FieldPosition[field=${getField()},attribute=${getFieldAttribute()},beginIndex=${getBeginIndex()},endIndex=${getEndIndex()}]" // scalastyle:off 44 | } 45 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/Format.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import AttributedCharacterIterator.Attribute 4 | 5 | abstract class Format protected () extends Cloneable { 6 | def format(obj: AnyRef): String = format(obj, new StringBuffer(), new FieldPosition(0)).toString 7 | 8 | def format(obj: AnyRef, toAppendTo: StringBuffer, pos: FieldPosition): StringBuffer 9 | 10 | def formatToCharacterIterator(obj: AnyRef): AttributedCharacterIterator = 11 | new Format.EmptyAttributedCharacterIterator 12 | 13 | def parseObject(source: String, pos: ParsePosition): AnyRef 14 | 15 | def parseObject(source: String): AnyRef = 16 | parseObject(source, new ParsePosition(0)) 17 | } 18 | 19 | object Format { 20 | private class EmptyAttributedCharacterIterator extends AttributedCharacterIterator { 21 | override def getAttributes(): java.util.Map[Attribute, AnyRef] = 22 | new java.util.HashMap() 23 | 24 | override def getAttribute(attribute: Attribute): AnyRef = null 25 | 26 | override def getAllAttributeKeys(): java.util.Set[Attribute] = 27 | new java.util.TreeSet() 28 | 29 | override def getRunLimit(): Int = 0 30 | 31 | override def getRunLimit(attribute: Attribute): Int = 0 32 | 33 | override def getRunLimit(attributes: java.util.Set[_ <: Attribute]): Int = 0 34 | 35 | override def getRunStart(): Int = 0 36 | 37 | override def getRunStart(attribute: Attribute): Int = 0 38 | 39 | override def getRunStart(attributes: java.util.Set[_ <: Attribute]): Int = 0 40 | 41 | override def next(): Char = 0 42 | 43 | override def setIndex(position: Int): Char = 0 44 | 45 | override def getIndex(): Int = 0 46 | 47 | override def last(): Char = 0 48 | 49 | override def getBeginIndex(): Int = 0 50 | 51 | override def getEndIndex(): Int = 0 52 | 53 | override def current(): Char = 0 54 | 55 | override def previous(): Char = 0 56 | 57 | override def first(): Char = 0 58 | } 59 | 60 | class Field protected (private[this] val name: String) 61 | extends AttributedCharacterIterator.Attribute(name) { 62 | 63 | override def toString(): String = s"${getClass.getName}($name)" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/NumberFormat.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import java.math.RoundingMode 4 | import java.util.{ Currency, Locale } 5 | import locales.cldr.{ LDML, NumberPatterns } 6 | import locales.LocalesDb 7 | 8 | abstract class NumberFormat protected () extends Format { 9 | private[this] var parseIntegerOnly: Boolean = false 10 | private[this] var roundingMode: RoundingMode = RoundingMode.HALF_EVEN 11 | 12 | // override def parseObject(source: String, pos: ParsePosition): AnyRef 13 | 14 | override def format(obj: AnyRef, toAppendTo: StringBuffer, pos: FieldPosition): StringBuffer = 15 | obj match { 16 | case n: Number if n.doubleValue() == n.longValue() => 17 | format(n.longValue(), toAppendTo, pos) 18 | case n: Number => 19 | format(n.doubleValue(), toAppendTo, pos) 20 | case _ => 21 | throw new IllegalArgumentException("Cannot format given Object as a Number") 22 | } 23 | 24 | final def format(number: Double): String = 25 | format(number, new StringBuffer, IgnoreFieldPosition).toString 26 | final def format(number: Long): String = 27 | format(number, new StringBuffer, IgnoreFieldPosition).toString 28 | 29 | def format(number: Double, toAppendTo: StringBuffer, pos: FieldPosition): StringBuffer 30 | def format(number: Long, toAppendTo: StringBuffer, pos: FieldPosition): StringBuffer 31 | 32 | // def parse(source: String, parsePosition: ParsePosition): Number 33 | // def parse(source: String): Number 34 | 35 | def isParseIntegerOnly(): Boolean = this.parseIntegerOnly 36 | def setParseIntegerOnly(value: Boolean): Unit = this.parseIntegerOnly = value 37 | 38 | def isGroupingUsed(): Boolean 39 | def setGroupingUsed(newValue: Boolean): Unit 40 | 41 | def getMaximumIntegerDigits(): Int 42 | def setMaximumIntegerDigits(newValue: Int): Unit 43 | 44 | def getMinimumIntegerDigits(): Int 45 | def setMinimumIntegerDigits(newValue: Int): Unit 46 | 47 | def getMaximumFractionDigits(): Int 48 | def setMaximumFractionDigits(newValue: Int): Unit 49 | 50 | def getMinimumFractionDigits(): Int 51 | def setMinimumFractionDigits(newValue: Int): Unit 52 | 53 | def getCurrency(): Currency 54 | def setCurrency(currency: Currency): Unit 55 | 56 | def getRoundingMode(): RoundingMode = roundingMode 57 | def setRoundingMode(mode: RoundingMode): Unit = this.roundingMode = mode 58 | } 59 | 60 | object NumberFormat { 61 | val INTEGER_FIELD: Int = 0 62 | val FRACTION_FIELD: Int = 1 63 | 64 | private def setup(nf: DecimalFormat): NumberFormat = { 65 | nf.setMaximumIntegerDigits(Integer.MAX_VALUE) 66 | nf 67 | } 68 | 69 | private def integerSetup(nf: DecimalFormat): NumberFormat = { 70 | setup(nf) 71 | nf.setMaximumFractionDigits(0) 72 | nf 73 | } 74 | 75 | private def percentSetup(nf: DecimalFormat): NumberFormat = { 76 | setup(nf) 77 | nf.setMaximumFractionDigits(0) 78 | nf.setMultiplier(100) 79 | nf 80 | } 81 | 82 | private def patternsR(ldml: LDML, get: NumberPatterns => Option[String]): Option[String] = 83 | get(ldml.numberPatterns).orElse(ldml.parent.flatMap(patternsR(_, get))) 84 | 85 | final def getInstance(): NumberFormat = getNumberInstance() 86 | 87 | def getInstance(inLocale: Locale): NumberFormat = getNumberInstance(inLocale) 88 | 89 | final def getNumberInstance(): NumberFormat = 90 | getInstance(Locale.getDefault(Locale.Category.FORMAT)) 91 | 92 | def getNumberInstance(inLocale: Locale): NumberFormat = 93 | LocalesDb 94 | .ldml(inLocale) 95 | .flatMap { ldml => 96 | val ptrn = patternsR(ldml, _.decimalFormat) 97 | ptrn.map(new DecimalFormat(_, DecimalFormatSymbols.getInstance(inLocale))).map(setup) 98 | } 99 | .getOrElse(new DecimalFormat("", DecimalFormatSymbols.getInstance(inLocale))) 100 | 101 | final def getIntegerInstance(): NumberFormat = 102 | getIntegerInstance(Locale.getDefault(Locale.Category.FORMAT)) 103 | 104 | def getIntegerInstance(inLocale: Locale): NumberFormat = { 105 | val f = LocalesDb 106 | .ldml(inLocale) 107 | .flatMap { ldml => 108 | val ptrn = patternsR(ldml, _.decimalFormat) 109 | ptrn 110 | .map(p => 111 | new DecimalFormat( 112 | p.substring(0, p.indexOf(".")), 113 | DecimalFormatSymbols.getInstance(inLocale) 114 | ) 115 | ) 116 | .map(integerSetup) 117 | } 118 | .getOrElse(new DecimalFormat("", DecimalFormatSymbols.getInstance(inLocale))) 119 | 120 | f.setParseIntegerOnly(true) 121 | 122 | f 123 | } 124 | 125 | final def getCurrencyInstance(): NumberFormat = 126 | getCurrencyInstance(Locale.getDefault(Locale.Category.FORMAT)) 127 | 128 | final def getCurrencyInstance(inLocale: Locale): NumberFormat = 129 | LocalesDb 130 | .ldml(inLocale) 131 | .flatMap { ldml => 132 | val ptrn = patternsR(ldml, _.currencyFormat) 133 | ptrn.map(new DecimalFormat(_, DecimalFormatSymbols.getInstance(inLocale))).map(setup) 134 | } 135 | .getOrElse(new DecimalFormat("", DecimalFormatSymbols.getInstance(inLocale))) 136 | 137 | final def getPercentInstance(): NumberFormat = 138 | getPercentInstance(Locale.getDefault(Locale.Category.FORMAT)) 139 | 140 | def getPercentInstance(inLocale: Locale): NumberFormat = 141 | LocalesDb 142 | .ldml(inLocale) 143 | .flatMap { ldml => 144 | val ptrn = patternsR(ldml, _.percentFormat) 145 | ptrn.map(new DecimalFormat(_, DecimalFormatSymbols.getInstance(inLocale))).map(percentSetup) 146 | } 147 | .getOrElse(new DecimalFormat("", DecimalFormatSymbols.getInstance(inLocale))) 148 | 149 | def getAvailableLocales(): Array[Locale] = Locale.getAvailableLocales() 150 | } 151 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/ParseException.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | class ParseException(private[this] val s: String, private[this] val errorOffset: Int) 4 | extends Exception(s) { 5 | 6 | def getErrorOffset(): Int = errorOffset 7 | } 8 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/ParsePosition.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | class ParsePosition(private[this] var index: Int) { 4 | private[this] var errorIndex: Int = -1 5 | 6 | def getIndex(): Int = index 7 | 8 | def setIndex(index: Int): Unit = this.index = index 9 | 10 | def setErrorIndex(ei: Int): Unit = this.errorIndex = ei 11 | 12 | def getErrorIndex(): Int = errorIndex 13 | 14 | override def equals(other: Any): Boolean = 15 | other match { 16 | case that: ParsePosition => 17 | getErrorIndex() == that.getErrorIndex() && 18 | getIndex() == that.getIndex() 19 | case _ => false 20 | } 21 | 22 | override def hashCode(): Int = { 23 | val state = Seq(getErrorIndex(), getIndex()) 24 | state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b) 25 | } 26 | 27 | override def toString(): String = 28 | s"java.text.ParsePosition[index=${getIndex()},errorIndex=${getErrorIndex()}]" 29 | } 30 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/text/SimpleDateFormat.scala: -------------------------------------------------------------------------------- 1 | package java.text 2 | 3 | import java.util.{ Date, Locale } 4 | 5 | class SimpleDateFormat( 6 | private[this] val pattern: String, 7 | private[this] var symbols: DateFormatSymbols 8 | ) extends DateFormat { 9 | def this(pattern: String) = this(pattern, DateFormatSymbols.getInstance()) 10 | 11 | def this(pattern: String, aLocale: Locale) = this(pattern, DateFormatSymbols.getInstance(aLocale)) 12 | 13 | // def this() = this("???", DecimalFormatSymbols.getInstance()) 14 | 15 | def format(date: Date): String = { 16 | val fields = Array( 17 | (date.getYear() + 1900).toString.reverse, 18 | (date.getMonth() + 1).toString.reverse, 19 | date.getDate().toString.reverse, 20 | date.getHours().toString.reverse, 21 | date.getMinutes().toString.reverse, 22 | date.getSeconds().toString.reverse, 23 | (date.getTime - Date.UTC(date.getYear(), 24 | date.getMonth(), 25 | date.getDay(), 26 | date.getHours(), 27 | date.getMinutes(), 28 | date.getSeconds() 29 | )).toString.reverse 30 | ) 31 | 32 | def pop(pos: Int): Char = 33 | if (fields(pos).size > 0) { 34 | val res = fields(pos)(0) 35 | fields(pos) = fields(pos).drop(1) 36 | res 37 | } else '0' 38 | 39 | var patternPos = pattern.length 40 | 41 | val sb = new StringBuffer() 42 | while (patternPos > 0) { 43 | patternPos -= 1 44 | 45 | pattern(patternPos) match { 46 | case 'G' => ??? 47 | case 'y' => sb.append(pop(0)) 48 | case 'Y' => ??? 49 | case 'M' => sb.append(pop(1)) 50 | case 'w' => ??? 51 | case 'W' => ??? 52 | case 'D' => ??? 53 | case 'd' => sb.append(pop(2)) 54 | case 'F' => ??? 55 | case 'E' => ??? 56 | case 'u' => ??? 57 | case 'a' => ??? 58 | case 'H' => sb.append(pop(3)) 59 | case 'k' => ??? 60 | case 'K' => ??? 61 | case 'h' => ??? 62 | case 'm' => sb.append(pop(4)) 63 | case 's' => sb.append(pop(5)) 64 | case 'S' => sb.append(pop(6)) 65 | case 'z' => ??? 66 | case 'Z' => ??? 67 | case 'X' => ??? 68 | case other => 69 | sb.append(other) 70 | } 71 | } 72 | 73 | sb.reverse.toString 74 | } 75 | 76 | override final def format( 77 | obj: AnyRef, 78 | toAppendTo: StringBuffer, 79 | pos: FieldPosition 80 | ): StringBuffer = ??? 81 | 82 | override final def parseObject(source: String, pos: ParsePosition): AnyRef = ??? 83 | 84 | // def set2DigitYearStart(startDate: Date): Unit 85 | // def get2DigitYearStart(): Date 86 | // override final def format(date: Date, toAppendTo: StringBuffer, pos: FieldPosition): StringBuffer 87 | // def formatToCharacterIterator(obj: AnyRef): AttributedCharacterIterator 88 | // def parse(text: String, pos: ParsePosition): Date 89 | def toPattern(): String = pattern 90 | // def toLocalizedPattern(): String = pattern 91 | // def applyPattern(pattern: String): Unit 92 | // def applyLocalizedPattern(pattern: String): Unit 93 | def getDateFormatSymbols(): DateFormatSymbols = symbols 94 | 95 | def setDateFormatSymbols(symbols: DateFormatSymbols): Unit = 96 | this.symbols = symbols 97 | 98 | // override def clone(): Any 99 | // override def hashCode(): Int 100 | // override def equals(obj: Any): Boolean 101 | } 102 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/java/util/Currency.scala: -------------------------------------------------------------------------------- 1 | package java.util 2 | 3 | import scala.collection.{ Map => SMap, Set => SSet } 4 | import locales.cldr.{ CurrencyDataFractionsInfo, CurrencyType } 5 | import locales.LocalesDb 6 | import scala.jdk.CollectionConverters._ 7 | 8 | object Currency { 9 | private val countryCodeToCurrencyCodeMap: SMap[String, String] = 10 | LocalesDb.currencydata.regions.map { r => 11 | r.countryCode -> r.currencies 12 | .find(_.to.isEmpty) 13 | .orElse(r.currencies.headOption) 14 | .map(_.currencyCode) 15 | .get 16 | }.toMap 17 | 18 | private val all: SSet[Currency] = LocalesDb.currencydata.currencyTypes.map { 19 | (currencyType: CurrencyType) => 20 | val fractions: CurrencyDataFractionsInfo = 21 | LocalesDb.currencydata.fractions 22 | .find(_.currencyCode == currencyType.currencyCode) 23 | .orElse(LocalesDb.currencydata.fractions.find(_.currencyCode == "DEFAULT")) 24 | .get 25 | 26 | val numericCode: Int = 27 | LocalesDb.currencydata.numericCodes 28 | .find(_.currencyCode == currencyType.currencyCode) 29 | .map(_.numericCode) 30 | .getOrElse(0) 31 | 32 | Currency( 33 | currencyType.currencyCode, 34 | numericCode, 35 | fractions.digits, 36 | currencyType.currencyName, 37 | None 38 | ) 39 | }.toSet 40 | 41 | private val currencyCodeMap: SMap[String, Currency] = 42 | all.toSeq.groupBy(_.getCurrencyCode()).map { 43 | case (currencyCode: String, matches: Seq[Currency]) => currencyCode -> matches.head 44 | } 45 | 46 | def getAvailableCurrencies(): java.util.Set[Currency] = all.asJava 47 | 48 | // NullPointerException - if locale or its country code is null 49 | // IllegalArgumentException - if the country of the given locale is not a supported ISO 3166 country code. 50 | def getInstance(locale: Locale): Currency = { 51 | if (locale.getCountry() == null || locale.getCountry().isEmpty) throw new NullPointerException 52 | 53 | countryCodeToCurrencyCodeMap 54 | .get(locale.getCountry()) 55 | .flatMap(currencyCodeMap.get) 56 | .getOrElse( 57 | throw new IllegalArgumentException(s"No currency available for ${locale.toLanguageTag()}") 58 | ) 59 | } 60 | 61 | def getInstance(currencyCode: String): Currency = currencyCodeMap(currencyCode) 62 | } 63 | 64 | final case class Currency private ( 65 | currencyCode: String, 66 | numericCode: Int, 67 | fractionDigits: Int, 68 | defaultName: String, 69 | currencyLocale: Option[Locale] 70 | ) { 71 | 72 | def defaultLocale: Locale = currencyLocale.getOrElse(Locale.getDefault()) 73 | 74 | // Gets the ISO 4217 currency code of this currency. 75 | def getCurrencyCode(): String = currencyCode 76 | 77 | // Gets the default number of fraction digits used with this currency. 78 | def getDefaultFractionDigits(): Int = fractionDigits 79 | 80 | // Gets the name that is suitable for displaying this currency for the default DISPLAY locale. 81 | def getDisplayName(): String = getDisplayName(defaultLocale) 82 | 83 | // Gets the name that is suitable for displaying this currency for the specified locale. 84 | def getDisplayName(locale: Locale): String = 85 | LocalesDb 86 | .ldml(locale) 87 | .flatMap { ldml => 88 | ldml.getNumberCurrencyDescription(currencyCode).find(_.count.isEmpty).map(_.name) 89 | } 90 | .getOrElse(currencyCode) 91 | 92 | // Returns the ISO 4217 numeric code of this currency. 93 | def getNumericCode(): Int = numericCode 94 | 95 | // Gets the symbol of this currency for the default DISPLAY locale. 96 | def getSymbol(): String = getSymbol(defaultLocale) 97 | 98 | // Gets the symbol of this currency for the specified locale. 99 | def getSymbol(locale: Locale): String = 100 | LocalesDb 101 | .ldml(locale) 102 | .flatMap { ldml => 103 | val symbols = ldml.getNumberCurrencySymbol(currencyCode) 104 | 105 | // TODO: might need a more sophisticated symbol-matcher 106 | // The tests from the JVM indicate we prefer the "wide" over "narrow" symbol 107 | 108 | symbols 109 | .find(_.alt.isEmpty) 110 | .orElse(symbols.find(_.alt.exists(_ == "narrow"))) 111 | .map(_.symbol) 112 | } 113 | .getOrElse(currencyCode) 114 | 115 | // Returns the ISO 4217 currency code of this currency. 116 | override def toString(): String = currencyCode 117 | } 118 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/locales/LocalesDb.scala: -------------------------------------------------------------------------------- 1 | package locales 2 | 3 | import locales.cldr.CLDRMetadata 4 | import locales.cldr.LDML 5 | import locales.cldr.LocalesProvider 6 | import locales.cldr.NumberingSystem 7 | import org.portablescala.reflect._ 8 | import java.util.Locale 9 | 10 | object LocalesDb { 11 | 12 | val provider: LocalesProvider = 13 | Reflect 14 | .lookupLoadableModuleClass("locales.cldr.data.LocalesProvider$", null) 15 | .map { m => 16 | m.loadModule() 17 | .asInstanceOf[LocalesProvider] 18 | } 19 | .getOrElse(locales.cldr.fallback.LocalesProvider) 20 | 21 | val root: LDML = provider.root 22 | 23 | val latn: NumberingSystem = provider.latn 24 | 25 | val ldmls = provider.ldmls 26 | 27 | val metadata: CLDRMetadata = provider.metadata 28 | 29 | val currencydata = provider.currencyData 30 | 31 | /** Attempts to give a Locale for the given tag if available 32 | */ 33 | def localeForLanguageTag(languageTag: String): Option[Locale] = 34 | // TODO Support alternative tags for the same locale 35 | if (languageTag == "und") 36 | Some(Locale.ROOT) 37 | else 38 | provider.ldmls.get(languageTag).map(_.toLocale) 39 | 40 | /** Returns the ldml for the given locale 41 | */ 42 | def ldml(locale: Locale): Option[LDML] = { 43 | val tag = 44 | if (locale.toLanguageTag() == "zh-CN") "zh-Hans-CN" 45 | else if (locale.toLanguageTag() == "zh-TW") "zh-Hant-TW" 46 | else locale.toLanguageTag() 47 | provider.ldmls.get(tag) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/locales/cldr/fallback/data/data.scala: -------------------------------------------------------------------------------- 1 | package locales.cldr.fallback.data 2 | 3 | import locales.cldr._ 4 | import locales.cldr.fallback.data.numericsystems.latn 5 | 6 | object _en 7 | extends LDML( 8 | Some(_root), 9 | LDMLLocale("en", None, None, None), 10 | None, 11 | List( 12 | Symbols(latn, 13 | None, 14 | Some('.'), 15 | Some(','), 16 | Some(';'), 17 | Some('%'), 18 | Some('-'), 19 | Some('‰'), 20 | Some("∞"), 21 | Some("NaN"), 22 | Some("E") 23 | ) 24 | ), 25 | Some( 26 | CalendarSymbols( 27 | List("January", 28 | "February", 29 | "March", 30 | "April", 31 | "May", 32 | "June", 33 | "July", 34 | "August", 35 | "September", 36 | "October", 37 | "November", 38 | "December" 39 | ), 40 | List("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"), 41 | List("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"), 42 | List("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"), 43 | List("AM", "PM"), 44 | List("BC", "AD") 45 | ) 46 | ), 47 | Some( 48 | CalendarPatterns( 49 | Map((0, "EEEE, MMMM d, y"), (1, "MMMM d, y"), (2, "MMM d, y"), (3, "M/d/yy")), 50 | Map((0, "h:mm:ss a zzzz"), (1, "h:mm:ss a z"), (2, "h:mm:ss a"), (3, "h:mm a")) 51 | ) 52 | ), 53 | List(), 54 | NumberPatterns(Some("#,##0.###"), Some("#,##0%"), Some("¤#,##0.00;(¤#,##0.00)")) 55 | ) 56 | 57 | object _root 58 | extends LDML( 59 | None, 60 | LDMLLocale("root", None, None, None), 61 | Some(latn), 62 | List( 63 | Symbols(latn, 64 | None, 65 | Some('.'), 66 | Some(','), 67 | Some(';'), 68 | Some('%'), 69 | Some('-'), 70 | Some('‰'), 71 | Some("∞"), 72 | Some("NaN"), 73 | Some("E") 74 | ) 75 | ), 76 | Some( 77 | CalendarSymbols( 78 | List("M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12"), 79 | List(), 80 | List("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"), 81 | List(), 82 | List(), 83 | List("BCE", "CE") 84 | ) 85 | ), 86 | Some( 87 | CalendarPatterns( 88 | Map((0, "y MMMM d, EEEE"), (1, "y MMMM d"), (2, "y MMM d"), (3, "y-MM-dd")), 89 | Map((0, "HH:mm:ss zzzz"), (1, "HH:mm:ss z"), (2, "HH:mm:ss"), (3, "HH:mm")) 90 | ) 91 | ), 92 | List(), 93 | NumberPatterns(Some("#,##0.###"), Some("#,##0%"), Some("¤ #,##0.00")) 94 | ) 95 | 96 | object _all_ { 97 | lazy val all: Array[LDML] = // Auto-generated code from CLDR definitions, don't edit 98 | Array(_en, _root) 99 | } 100 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/locales/cldr/fallback/data/numericsystems.scala: -------------------------------------------------------------------------------- 1 | package locales.cldr.fallback.data 2 | 3 | // Auto-generated code from CLDR definitions, don't edit 4 | import locales.cldr.NumberingSystem 5 | object numericsystems { 6 | lazy val latn: NumberingSystem = 7 | NumberingSystem("latn", List('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')) 8 | } 9 | -------------------------------------------------------------------------------- /core/shared/src/main/scala/locales/cldr/fallback/ldmlprovider.scala: -------------------------------------------------------------------------------- 1 | package locales.cldr.fallback 2 | 3 | import locales.cldr._ 4 | 5 | object LocalesProvider extends LocalesProvider { 6 | def root: LDML = data._root 7 | def ldmls: Map[String, LDML] = 8 | data._all_.all.map { case l => 9 | (l.languageTag, l) 10 | }.toMap 11 | def latn: NumberingSystem = data.numericsystems.latn 12 | def currencyData: CurrencyData = 13 | new CurrencyData( 14 | Seq.empty, 15 | Seq.empty, 16 | Seq.empty, 17 | Seq.empty 18 | ) 19 | def metadata: CLDRMetadata = 20 | new CLDRMetadata(Array.empty, Map.empty, Array.empty, Map.empty, Array.empty) 21 | } 22 | -------------------------------------------------------------------------------- /demo/shared/src/main/scala/demo/DemoApp.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import java.text.DecimalFormatSymbols 4 | import java.text.NumberFormat 5 | import java.util.Locale 6 | 7 | object DemoApp { 8 | def main(args: Array[String]): Unit = { 9 | println(Locale.getDefault.toLanguageTag) 10 | // Minimal demo used for sanity checks and for size improvements 11 | val fi_FI = Locale.forLanguageTag("fi-FI") 12 | val dfs = DecimalFormatSymbols.getInstance(fi_FI) 13 | println(dfs) 14 | Locale.setDefault(fi_FI) 15 | println(NumberFormat.getNumberInstance().getMaximumFractionDigits()) 16 | println(fi_FI.toLanguageTag()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "devshell": { 4 | "inputs": { 5 | "flake-utils": "flake-utils", 6 | "nixpkgs": "nixpkgs" 7 | }, 8 | "locked": { 9 | "lastModified": 1655976588, 10 | "narHash": "sha256-VreHyH6ITkf/1EX/8h15UqhddJnUleb0HgbC3gMkAEQ=", 11 | "owner": "numtide", 12 | "repo": "devshell", 13 | "rev": "899ca4629020592a13a46783587f6e674179d1db", 14 | "type": "github" 15 | }, 16 | "original": { 17 | "owner": "numtide", 18 | "repo": "devshell", 19 | "type": "github" 20 | } 21 | }, 22 | "flake-compat": { 23 | "flake": false, 24 | "locked": { 25 | "lastModified": 1650374568, 26 | "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", 27 | "owner": "edolstra", 28 | "repo": "flake-compat", 29 | "rev": "b4a34015c698c7793d592d66adbab377907a2be8", 30 | "type": "github" 31 | }, 32 | "original": { 33 | "owner": "edolstra", 34 | "repo": "flake-compat", 35 | "type": "github" 36 | } 37 | }, 38 | "flake-utils": { 39 | "locked": { 40 | "lastModified": 1642700792, 41 | "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=", 42 | "owner": "numtide", 43 | "repo": "flake-utils", 44 | "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba", 45 | "type": "github" 46 | }, 47 | "original": { 48 | "owner": "numtide", 49 | "repo": "flake-utils", 50 | "type": "github" 51 | } 52 | }, 53 | "flake-utils_2": { 54 | "locked": { 55 | "lastModified": 1656065134, 56 | "narHash": "sha256-oc6E6ByIw3oJaIyc67maaFcnjYOz1mMcOtHxbEf9NwQ=", 57 | "owner": "numtide", 58 | "repo": "flake-utils", 59 | "rev": "bee6a7250dd1b01844a2de7e02e4df7d8a0a206c", 60 | "type": "github" 61 | }, 62 | "original": { 63 | "owner": "numtide", 64 | "repo": "flake-utils", 65 | "type": "github" 66 | } 67 | }, 68 | "nixpkgs": { 69 | "locked": { 70 | "lastModified": 1643381941, 71 | "narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=", 72 | "owner": "NixOS", 73 | "repo": "nixpkgs", 74 | "rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5", 75 | "type": "github" 76 | }, 77 | "original": { 78 | "owner": "NixOS", 79 | "ref": "nixpkgs-unstable", 80 | "repo": "nixpkgs", 81 | "type": "github" 82 | } 83 | }, 84 | "nixpkgs_2": { 85 | "locked": { 86 | "lastModified": 1656250965, 87 | "narHash": "sha256-2IlNf6jxEJiuCrGymqLOLjxk2SIj4HhVIwEb0kvcs24=", 88 | "owner": "nixos", 89 | "repo": "nixpkgs", 90 | "rev": "9a17f325397d137ac4d219ecbd5c7f15154422f4", 91 | "type": "github" 92 | }, 93 | "original": { 94 | "owner": "nixos", 95 | "ref": "nixpkgs-unstable", 96 | "repo": "nixpkgs", 97 | "type": "github" 98 | } 99 | }, 100 | "root": { 101 | "inputs": { 102 | "flake-utils": [ 103 | "typelevel-nix", 104 | "flake-utils" 105 | ], 106 | "nixpkgs": [ 107 | "typelevel-nix", 108 | "nixpkgs" 109 | ], 110 | "typelevel-nix": "typelevel-nix" 111 | } 112 | }, 113 | "typelevel-nix": { 114 | "inputs": { 115 | "devshell": "devshell", 116 | "flake-compat": "flake-compat", 117 | "flake-utils": "flake-utils_2", 118 | "nixpkgs": "nixpkgs_2" 119 | }, 120 | "locked": { 121 | "lastModified": 1656425080, 122 | "narHash": "sha256-hP05UVxhRkHymzV1+3UW9qB0+wRcgjQ2njAWOgFdhTU=", 123 | "owner": "typelevel", 124 | "repo": "typelevel-nix", 125 | "rev": "94a7c1b87b55e56d040799ac3eab772341088029", 126 | "type": "github" 127 | }, 128 | "original": { 129 | "owner": "typelevel", 130 | "repo": "typelevel-nix", 131 | "type": "github" 132 | } 133 | } 134 | }, 135 | "root": "root", 136 | "version": 7 137 | } 138 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | typelevel-nix.url = "github:typelevel/typelevel-nix"; 4 | nixpkgs.follows = "typelevel-nix/nixpkgs"; 5 | flake-utils.follows = "typelevel-nix/flake-utils"; 6 | }; 7 | 8 | outputs = { self, nixpkgs, flake-utils, typelevel-nix }: 9 | flake-utils.lib.eachDefaultSystem (system: 10 | let 11 | pkgs-x86_64 = import nixpkgs { system = "x86_64-darwin"; }; 12 | scala-cli-overlay = final: prev: { scala-cli = pkgs-x86_64.scala-cli; }; 13 | pkgs = import nixpkgs { 14 | inherit system; 15 | overlays = [ typelevel-nix.overlay scala-cli-overlay ]; 16 | }; 17 | in { 18 | devShell = pkgs.devshell.mkShell { 19 | imports = [ typelevel-nix.typelevelShell ]; 20 | typelevelShell = { 21 | nodejs.enable = true; 22 | jdk.package = pkgs.jdk8; 23 | }; 24 | }; 25 | } 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /localesFullDb/shared/src/main/scala/package.scala: -------------------------------------------------------------------------------- 1 | package locales 2 | 3 | object LocalesDB {} 4 | -------------------------------------------------------------------------------- /macroutils/src/main/scala-2.11/JVMDate.scala: -------------------------------------------------------------------------------- 1 | package io.github.cquiroz.utils 2 | 3 | import java.util.Date 4 | import scala.language.experimental.macros 5 | import scala.reflect.macros.whitebox.Context 6 | 7 | object JVMDate { 8 | type JVMDateT = { 9 | val year: Int 10 | val month: Int 11 | val date: Int 12 | val hours: Int 13 | val minutes: Int 14 | val seconds: Int 15 | } 16 | 17 | def getImpl(c: Context)(time: c.Expr[Long]) = { 18 | import c.universe._ 19 | 20 | val timeValue = TreeHelper.resetLong(c)(time) 21 | 22 | val date = new Date(c.eval[Long](timeValue)) 23 | 24 | c.Expr[JVMDateT](q""" 25 | new { 26 | val year: Int = ${date.getYear} 27 | val month: Int = ${date.getMonth} 28 | val date: Int = ${date.getDate} 29 | val hours: Int = ${date.getHours} 30 | val minutes: Int = ${date.getMinutes} 31 | val seconds: Int = ${date.getSeconds} 32 | } 33 | """) 34 | } 35 | 36 | def get(time: Long): JVMDateT = macro getImpl 37 | } 38 | -------------------------------------------------------------------------------- /macroutils/src/main/scala-2.11/TreeHelper.scala: -------------------------------------------------------------------------------- 1 | package io.github.cquiroz.utils 2 | 3 | import scala.reflect.macros.whitebox.Context 4 | 5 | object TreeHelper { 6 | 7 | def resetLong(c: Context)(v: c.Expr[Long]): c.Expr[Long] = 8 | c.Expr[Long](v.tree.duplicate) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /macroutils/src/main/scala-2.12/JVMDate.scala: -------------------------------------------------------------------------------- 1 | package io.github.cquiroz.utils 2 | 3 | import java.util.Date 4 | import scala.language.experimental.macros 5 | import scala.reflect.macros.whitebox.Context 6 | 7 | object JVMDate { 8 | type JVMDateT = { 9 | val year: Int 10 | val month: Int 11 | val date: Int 12 | val hours: Int 13 | val minutes: Int 14 | val seconds: Int 15 | } 16 | 17 | def getImpl(c: Context)(time: c.Expr[Long]) = { 18 | import c.universe._ 19 | 20 | val timeValue = TreeHelper.resetLong(c)(time) 21 | 22 | val date = new Date(c.eval[Long](timeValue)) 23 | 24 | c.Expr[JVMDateT](q""" 25 | new { 26 | val year: Int = ${date.getYear} 27 | val month: Int = ${date.getMonth} 28 | val date: Int = ${date.getDate} 29 | val hours: Int = ${date.getHours} 30 | val minutes: Int = ${date.getMinutes} 31 | val seconds: Int = ${date.getSeconds} 32 | } 33 | """) 34 | } 35 | 36 | def get(time: Long): JVMDateT = macro getImpl 37 | } 38 | -------------------------------------------------------------------------------- /macroutils/src/main/scala-2.12/TreeHelper.scala: -------------------------------------------------------------------------------- 1 | package io.github.cquiroz.utils 2 | 3 | import scala.reflect.macros.whitebox.Context 4 | 5 | object TreeHelper { 6 | 7 | def resetLong(c: Context)(v: c.Expr[Long]): c.Expr[Long] = 8 | c.Expr[Long](v.tree.duplicate) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /macroutils/src/main/scala-2.13/JVMDate.scala: -------------------------------------------------------------------------------- 1 | package io.github.cquiroz.utils 2 | 3 | import java.util.Date 4 | import scala.reflect.macros.whitebox.Context 5 | 6 | object JVMDate { 7 | type JVMDateT = { 8 | val year: Int 9 | val month: Int 10 | val date: Int 11 | val hours: Int 12 | val minutes: Int 13 | val seconds: Int 14 | } 15 | 16 | def getImpl(c: Context)(time: c.Expr[Long]) = { 17 | import c.universe._ 18 | 19 | val timeValue = TreeHelper.resetLong(c)(time) 20 | 21 | val date = new Date(c.eval[Long](timeValue)) 22 | 23 | c.Expr[JVMDateT](q""" 24 | new { 25 | val year: Int = ${date.getYear} 26 | val month: Int = ${date.getMonth} 27 | val date: Int = ${date.getDate} 28 | val hours: Int = ${date.getHours} 29 | val minutes: Int = ${date.getMinutes} 30 | val seconds: Int = ${date.getSeconds} 31 | } 32 | """) 33 | } 34 | 35 | def get(time: Long): JVMDateT = macro getImpl 36 | } 37 | -------------------------------------------------------------------------------- /macroutils/src/main/scala-2.13/TreeHelper.scala: -------------------------------------------------------------------------------- 1 | package io.github.cquiroz.utils 2 | 3 | import scala.reflect.macros.whitebox.Context 4 | 5 | object TreeHelper { 6 | 7 | def resetLong(c: Context)(v: c.Expr[Long]): c.Expr[Long] = 8 | c.Expr[Long](v.tree.duplicate) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /macroutils/src/main/scala-3/JVMDate.scala: -------------------------------------------------------------------------------- 1 | package io.github.cquiroz.utils 2 | 3 | object JVMDate: 4 | type JVMDateT = { 5 | val year: Int 6 | val month: Int 7 | val date: Int 8 | val hours: Int 9 | val minutes: Int 10 | val seconds: Int 11 | } 12 | 13 | inline def get(time: Long): JVMDateT = { 14 | val curDate = new java.util.Date(time) 15 | new { 16 | val year: Int = curDate.getYear 17 | val month: Int = curDate.getMonth 18 | val date: Int = curDate.getDate 19 | val hours: Int = curDate.getHours 20 | val minutes: Int = curDate.getMinutes 21 | val seconds: Int = curDate.getSeconds 22 | } 23 | } 24 | end JVMDate 25 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.11 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") 2 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") 3 | addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") 4 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.2") 5 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2") 6 | addSbtPlugin("io.github.cquiroz" % "sbt-locales" % "4.5.0") 7 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12") 8 | addSbtPlugin("org.typelevel" % "sbt-tpolecat" % "0.5.1") 9 | addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.23.0") 10 | -------------------------------------------------------------------------------- /tests/js-jvm/src/test/scala/testsuite/javalib/text/SimpleDateFormatTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.text 2 | 3 | import java.util.Date 4 | import java.text.SimpleDateFormat 5 | 6 | import io.github.cquiroz.utils.JVMDate 7 | import scala.language.reflectiveCalls 8 | 9 | class SimpleDateFormatTest extends munit.FunSuite { 10 | final val time = 1491381282242L 11 | val date = new Date(time) 12 | val jvmDate = JVMDate.get(time) 13 | val year = totalSize(1900 + jvmDate.year.toLong, 4) 14 | val month = totalSize(1 + jvmDate.month.toLong, 2) 15 | val day = totalSize(jvmDate.date.toLong, 2) 16 | val hours = totalSize(jvmDate.hours.toLong, 2) 17 | val minutes = totalSize(jvmDate.minutes.toLong, 2) 18 | val seconds = totalSize(jvmDate.seconds.toLong, 2) 19 | val millis = totalSize( 20 | date.getTime - Date.UTC( 21 | date.getYear, 22 | date.getMonth, 23 | date.getDay, 24 | date.getHours, 25 | date.getMinutes, 26 | date.getSeconds 27 | ), 28 | 3 29 | ) 30 | 31 | def totalSize(num: Long, size: Int): String = { 32 | val s: String = num.toString 33 | 34 | if (s.size > size) s.substring(s.size - size) 35 | else s.reverse.padTo(size, '0').reverse 36 | } 37 | 38 | test("year_format") { 39 | val f = new SimpleDateFormat("yyyy") 40 | assertEquals(year, f.format(date)) 41 | } 42 | 43 | test("month_format") { 44 | val f = new SimpleDateFormat("MM") 45 | assertEquals(month, f.format(date)) 46 | } 47 | 48 | test("day_format") { 49 | val f = new SimpleDateFormat("dd") 50 | assertEquals(day, f.format(date)) 51 | } 52 | 53 | test("hour_format") { 54 | val f = new SimpleDateFormat("HH") 55 | assertEquals(hours, f.format(date)) 56 | } 57 | 58 | test("minutes_format") { 59 | val f = new SimpleDateFormat("mm") 60 | assertEquals(minutes, f.format(date)) 61 | } 62 | 63 | test("seconds_format") { 64 | val f = new SimpleDateFormat("ss") 65 | assertEquals(seconds, f.format(date)) 66 | } 67 | 68 | test("millis_format") { 69 | val f = new SimpleDateFormat("SSS") 70 | assertEquals(millis, f.format(date)) 71 | } 72 | 73 | test("full_date_format") { 74 | val f = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss:SSS") 75 | assertEquals(s"$day/$month/$year $hours:$minutes:$seconds:$millis", f.format(date)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/js-native/src/test/scala/testsuite/locales/BCP47Test.scala: -------------------------------------------------------------------------------- 1 | package testsuite.locales 2 | 3 | import locales.BCP47 4 | import locales.BCP47.LanguageTag 5 | import locales.cldr.data.metadata._ 6 | 7 | class BCP47Test extends munit.FunSuite { 8 | test("test_language") { 9 | isoLanguages.map(BCP47.parseTag).zip(isoLanguages).foreach { 10 | case (Some(LanguageTag(lang, None, None, None, Nil, Nil, None)), l) => 11 | assertEquals(lang, l) 12 | 13 | case _ => 14 | fail("Shouldn't happen") 15 | } 16 | } 17 | 18 | test("test_language_region") { 19 | val tags = for { 20 | l <- isoLanguages 21 | r <- isoCountries 22 | } yield (l, r, s"$l-$r") 23 | tags.map { case (l, r, t) => (l, r, BCP47.parseTag(t)) }.foreach { 24 | case (l, r, Some(LanguageTag(lang, None, None, region, Nil, Nil, None))) => 25 | assertEquals(l, lang) 26 | assertEquals(Some(r): Option[String], region) 27 | 28 | case _ => 29 | fail("Shouldn't happen") 30 | } 31 | } 32 | 33 | test("test_language_region_script") { 34 | val tags = for { 35 | l <- isoLanguages 36 | r <- isoCountries 37 | s <- scripts 38 | } yield (l, r, s, s"$l-$s-$r") 39 | tags.map { case (l, r, s, t) => (l, r, s, BCP47.parseTag(t)) }.foreach { 40 | case (l, r, s, Some(LanguageTag(lang, None, script, region, Nil, Nil, None))) => 41 | assertEquals(l, lang) 42 | assertEquals(Some(r): Option[String], region) 43 | assertEquals(Some(s): Option[String], script) 44 | 45 | case _ => 46 | fail("Shouldn't happen") 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/text/AttributedCharacterIteratorTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.text 2 | 3 | import java.text.AttributedCharacterIterator.Attribute 4 | 5 | class AttributedCharacterIteratorTest extends munit.FunSuite { 6 | test("static_value_to_string") { 7 | assertEquals( 8 | s"java.text.AttributedCharacterIterator$$Attribute(language)", 9 | Attribute.LANGUAGE.toString 10 | ) 11 | assertEquals( 12 | s"java.text.AttributedCharacterIterator$$Attribute(reading)", 13 | Attribute.READING.toString 14 | ) 15 | assertEquals( 16 | s"java.text.AttributedCharacterIterator$$Attribute(input_method_segment)", 17 | Attribute.INPUT_METHOD_SEGMENT.toString 18 | ) 19 | } 20 | 21 | test("equals") { 22 | assertEquals(Attribute.LANGUAGE, Attribute.LANGUAGE) 23 | assert(Attribute.READING != Attribute.LANGUAGE) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/text/CharacterIteratorTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.text 2 | 3 | import java.text.CharacterIterator 4 | 5 | class CharacterIteratorTest extends munit.FunSuite { 6 | test("done") { 7 | assertEquals('\uFFFF', CharacterIterator.DONE) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/text/DecimalFormatSymbolsTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.text 2 | 3 | import java.text.DecimalFormatSymbols 4 | import java.util.Locale 5 | import testsuite.utils.Platform 6 | 7 | class DecimalFormatSymbolsTest extends munit.FunSuite { 8 | 9 | case class LocaleTestItem(tag: String, cldr21: Boolean = false) 10 | 11 | val englishSymbols = List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E") 12 | 13 | val standardLocalesData = List( 14 | Locale.ROOT -> 15 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 16 | Locale.ENGLISH -> englishSymbols, 17 | Locale.GERMAN -> 18 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 19 | Locale.ITALIAN -> 20 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 21 | Locale.KOREAN -> 22 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 23 | Locale.CHINESE -> 24 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 25 | Locale.SIMPLIFIED_CHINESE -> 26 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 27 | Locale.TRADITIONAL_CHINESE -> 28 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "非數值", "-", "E"), 29 | Locale.GERMANY -> 30 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 31 | Locale.ITALY -> 32 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 33 | Locale.KOREA -> 34 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 35 | Locale.CHINA -> 36 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 37 | Locale.PRC -> 38 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 39 | Locale.TAIWAN -> 40 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "非數值", "-", "E"), 41 | Locale.UK -> 42 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 43 | Locale.US -> 44 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 45 | Locale.CANADA_FRENCH -> 46 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "NaN", "-", "E") 47 | ) 48 | 49 | val extraLocalesData = List( 50 | // af uses latn 51 | LocaleTestItem("af") -> 52 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 53 | LocaleTestItem("az") -> 54 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 55 | LocaleTestItem("az-Cyrl") -> 56 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 57 | LocaleTestItem("es-CL") -> 58 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 59 | LocaleTestItem("zh") -> 60 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 61 | LocaleTestItem("zh-Hant") -> 62 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "非數值", "-", "E") 63 | ) 64 | 65 | // These locales show differences with Java due to a different CLDR version 66 | val localesDiff = List( 67 | // ar has a default arab set of symbols 68 | LocaleTestItem("ar", cldr21 = true) -> 69 | List("٠", "٫", "٬", "؉", "٪", "#", "؛", "∞", "ليس رقم", "\u002D", "اس"), // JVM 70 | LocaleTestItem("ar") -> 71 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "ليس رقمًا", "-", "E"), // JS 72 | // bn has a default ns but it is a latn alias 73 | LocaleTestItem("bn", cldr21 = true) -> 74 | List("\u09e6", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 75 | // bn has a default ns but it is a latn alias 76 | LocaleTestItem("bn") -> 77 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 78 | LocaleTestItem("fr", cldr21 = true) -> 79 | List("0", ",", "\u202F", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 80 | LocaleTestItem("fr", cldr21 = false) -> 81 | List("0", ",", "\u202F", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 82 | LocaleTestItem("it-CH", cldr21 = true) -> 83 | List("0", ".", "\u2019", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 84 | LocaleTestItem("it-CH") -> 85 | List("0", ".", "’", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 86 | // fa uses arabext 87 | LocaleTestItem("fa", cldr21 = true) -> 88 | List("۰", "٫", "٬", "؉", "٪", "#", "؛", "∞", "ناعدد", "\u2212", "×۱۰^"), // JVM 89 | LocaleTestItem("fa") -> 90 | List("۰", "٫", "٬", "؉", "٪", "#", "؛", "∞", "ناعدد", "−", "×۱۰^"), // JS 91 | LocaleTestItem("fi-FI", cldr21 = true) -> 92 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "epäluku", "\u2212", "E"), 93 | LocaleTestItem("fi-FI", cldr21 = false) -> 94 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "epäluku", "−", "E"), 95 | LocaleTestItem("ja", cldr21 = true) -> 96 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), // JVM 97 | LocaleTestItem("ja") -> 98 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), // JS 99 | LocaleTestItem("ka", cldr21 = true) -> 100 | List("0", ",", "\u00a0", "‰", "%", "#", ";", "∞", "არ არის რიცხვი", "-", "E"), // JVM 101 | LocaleTestItem("ka") -> 102 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "არ არის რიცხვი", "-", "E"), // JS 103 | LocaleTestItem("lv", cldr21 = true) -> 104 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "NS", "-", "E"), // JVM 105 | LocaleTestItem("lv") -> 106 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "NS", "-", "E"), // JS 107 | LocaleTestItem("my", cldr21 = true) -> 108 | List("၀", ".", ",", "‰", "%", "#", "၊", "∞", "ဂဏန်းမဟုတ်သော", "-", "E"), // JVM 109 | LocaleTestItem("my") -> 110 | List("0", ".", ",", "‰", "%", "#", ";", "∞", "ဂဏန်းမဟုတ်သော", "-", "E"), // JS 111 | LocaleTestItem("smn", cldr21 = true) -> 112 | List("0", ",", "\u00a0", "‰", "%", "#", ";", "∞", "epiloho", "-", "E"), // JVM 113 | LocaleTestItem("smn") -> 114 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "epiloho", "-", "E"), // JS 115 | LocaleTestItem("smn-FI", cldr21 = true) -> 116 | List("0", ",", "\u00a0", "‰", "%", "#", ";", "∞", "epiloho", "-", "E"), 117 | LocaleTestItem("smn-FI") -> 118 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "epiloho", "-", "E"), 119 | LocaleTestItem("ru-RU", cldr21 = true) -> 120 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "не число", "-", "E"), 121 | LocaleTestItem("ru-RU") -> 122 | List("0", ",", "\u00A0", "‰", "%", "#", ";", "∞", "не число", "-", "E"), 123 | LocaleTestItem("ca", cldr21 = true) -> 124 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E"), 125 | LocaleTestItem("ca") -> 126 | List("0", ",", ".", "‰", "%", "#", ";", "∞", "NaN", "-", "E") 127 | ) 128 | 129 | def test_dfs(dfs: DecimalFormatSymbols, symbols: List[String]): Unit = { 130 | assertEquals(symbols(0).charAt(0), dfs.getZeroDigit) 131 | assertEquals(symbols(1).charAt(0), dfs.getDecimalSeparator) 132 | assertEquals(symbols(2).charAt(0), dfs.getGroupingSeparator) 133 | assertEquals(symbols(3).charAt(0), dfs.getPerMill) 134 | assertEquals(symbols(4).charAt(0), dfs.getPercent) 135 | assertEquals(symbols(5).charAt(0), dfs.getDigit) 136 | assertEquals(symbols(6).charAt(0), dfs.getPatternSeparator) 137 | assertEquals(symbols(7), dfs.getInfinity) 138 | assertEquals(symbols(8), dfs.getNaN) 139 | assertEquals(symbols(9).charAt(0), dfs.getMinusSign) 140 | assertEquals(symbols(10), dfs.getExponentSeparator) 141 | } 142 | 143 | test("default_locales_decimal_format_symbol") { 144 | assume(!sys.props.get("java.version").exists(_.startsWith("1.8."))) 145 | standardLocalesData.foreach { case (l, symbols) => 146 | val dfs = DecimalFormatSymbols.getInstance(l) 147 | test_dfs(dfs, symbols) 148 | } 149 | } 150 | 151 | test("extra_locales_decimal_format_symbol") { 152 | assume(!sys.props.get("java.version").exists(_.startsWith("1.8."))) 153 | extraLocalesData.foreach { case (LocaleTestItem(tag, _), symbols) => 154 | val l = Locale.forLanguageTag(tag) 155 | val dfs = DecimalFormatSymbols.getInstance(l) 156 | test_dfs(dfs, symbols) 157 | } 158 | } 159 | 160 | // These tests give the same data on CLDR 21 161 | test("extra_locales_not_agreeing_decimal_format_symbol") { 162 | assume(!sys.props.get("java.version").exists(_.startsWith("1.8."))) 163 | localesDiff.foreach { case (LocaleTestItem(tag, cldr21), symbols) => 164 | val l = Locale.forLanguageTag(tag) 165 | val dfs = DecimalFormatSymbols.getInstance(l) 166 | if (Platform.executingInJVM && cldr21) 167 | test_dfs(dfs, symbols) 168 | if (!Platform.executingInJVM && !cldr21) 169 | test_dfs(dfs, symbols) 170 | } 171 | } 172 | 173 | test("available_locales") { 174 | val initial = DecimalFormatSymbols.getAvailableLocales.length 175 | assert(initial > 0) 176 | } 177 | 178 | test("defaults") { 179 | assume(!sys.props.get("java.version").exists(v => v.startsWith("1.8.") || v == "1.8")) 180 | val dfs = new DecimalFormatSymbols() 181 | test_dfs(dfs, englishSymbols) 182 | } 183 | 184 | test("setters") { 185 | val dfs = new DecimalFormatSymbols() 186 | dfs.setZeroDigit('1') 187 | assertEquals('1', dfs.getZeroDigit) 188 | dfs.setGroupingSeparator('1') 189 | assertEquals('1', dfs.getGroupingSeparator) 190 | dfs.setDecimalSeparator('1') 191 | assertEquals('1', dfs.getDecimalSeparator) 192 | dfs.setPerMill('1') 193 | assertEquals('1', dfs.getPerMill) 194 | dfs.setPercent('1') 195 | assertEquals('1', dfs.getPercent) 196 | dfs.setDigit('1') 197 | assertEquals('1', dfs.getDigit) 198 | dfs.setPatternSeparator('1') 199 | assertEquals('1', dfs.getPatternSeparator) 200 | dfs.setMinusSign('1') 201 | assertEquals('1', dfs.getMinusSign) 202 | 203 | dfs.setInfinity(null) 204 | assert(null == dfs.getInfinity) 205 | dfs.setInfinity("Inf") 206 | assertEquals("Inf", dfs.getInfinity) 207 | 208 | dfs.setNaN(null) 209 | assert(null == dfs.getNaN) 210 | dfs.setNaN("nan") 211 | assertEquals("nan", dfs.getNaN) 212 | 213 | intercept[NullPointerException](dfs.setExponentSeparator(null)) 214 | dfs.setExponentSeparator("exp") 215 | assertEquals("exp", dfs.getExponentSeparator) 216 | } 217 | 218 | test("clone") { 219 | val dfs = new DecimalFormatSymbols() 220 | assertEquals(dfs, dfs.clone().asInstanceOf[DecimalFormatSymbols]) 221 | assert(dfs ne dfs.clone()) 222 | } 223 | 224 | test("equals") { 225 | val dfs = new DecimalFormatSymbols() 226 | assertEquals(dfs, dfs) 227 | assert(dfs eq dfs) 228 | assert(!dfs.equals(null)) 229 | assert(!dfs.equals(1)) 230 | val dfs2 = new DecimalFormatSymbols() 231 | assertEquals(dfs, dfs2) 232 | assert(dfs ne dfs2) 233 | dfs2.setDigit('i') 234 | assert(!dfs.equals(dfs2)) 235 | } 236 | 237 | test("bad_tag_matches_root_dfs") { 238 | assume(!sys.props.get("java.version").exists(_.startsWith("1.8."))) 239 | val l = Locale.forLanguageTag("no_NO") 240 | val dfs = DecimalFormatSymbols.getInstance(l) 241 | standardLocalesData.foreach { 242 | case (Locale.ROOT, symbols) => 243 | test_dfs(dfs, symbols) 244 | case (_, _) => 245 | } 246 | } 247 | 248 | // test("hash_code") { 249 | // val dfs = new DecimalFormatSymbols() 250 | // assertEquals(dfs.hashCode, dfs.hashCode) 251 | // val dfs2 = new DecimalFormatSymbols() 252 | // assertEquals(dfs.hashCode, dfs2.hashCode) 253 | // dfs2.setExponentSeparator("abc") 254 | // // These tests should fail but they pass on the JVM 255 | // assertEquals(dfs.hashCode, dfs2.hashCode) 256 | // standardLocalesData.filter(_._1 != Locale.ROOT).foreach { 257 | // case (l, symbols) => 258 | // val df = DecimalFormatSymbols.getInstance(l) 259 | // assert(!dfs.hashCode.equals(df.hashCode)) 260 | // } 261 | // } 262 | 263 | } 264 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/text/FieldPositionTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.text 2 | 3 | import java.text.{ FieldPosition, Format } 4 | 5 | final case class TestField(name: String) extends Format.Field(name) 6 | 7 | class FieldPositionTest extends munit.FunSuite { 8 | 9 | test("constructors") { 10 | val field = TestField("abc") 11 | 12 | val f1 = new FieldPosition(field, 1) 13 | assertEquals(field: Format.Field, f1.getFieldAttribute()) 14 | assertEquals(1, f1.getField()) 15 | assertEquals(0, f1.getBeginIndex()) 16 | assertEquals(0, f1.getEndIndex()) 17 | 18 | val f2 = new FieldPosition(field) 19 | assertEquals(field: Format.Field, f2.getFieldAttribute()) 20 | assertEquals(-1, f2.getField()) 21 | assertEquals(0, f2.getBeginIndex()) 22 | assertEquals(0, f2.getEndIndex()) 23 | 24 | val f3 = new FieldPosition(1) 25 | assert(null == f3.getFieldAttribute()) 26 | assertEquals(1, f3.getField()) 27 | assertEquals(0, f3.getBeginIndex()) 28 | assertEquals(0, f3.getEndIndex()) 29 | } 30 | 31 | test("begin_end_index") { 32 | val field = TestField("abc") 33 | 34 | val f1 = new FieldPosition(field, 1) 35 | f1.setBeginIndex(10) 36 | assertEquals(10, f1.getBeginIndex()) 37 | f1.setBeginIndex(-10) 38 | assertEquals(-10, f1.getBeginIndex()) 39 | 40 | f1.setEndIndex(10) 41 | assertEquals(10, f1.getEndIndex()) 42 | f1.setEndIndex(-10) 43 | assertEquals(-10, f1.getEndIndex()) 44 | } 45 | 46 | test("equals_hash_code") { 47 | val field = TestField("abc") 48 | 49 | val f1 = new FieldPosition(field, 1) 50 | assertEquals(f1, f1) 51 | assertEquals(f1.hashCode(), f1.hashCode()) 52 | 53 | val f2 = new FieldPosition(field, 1) 54 | assertEquals(f1, f2) 55 | assertEquals(f1.hashCode(), f2.hashCode()) 56 | 57 | val f3 = new FieldPosition(field) 58 | assert(!f1.equals(f3)) 59 | assert(f1.hashCode() != f3.hashCode()) 60 | 61 | val f4 = new FieldPosition(1) 62 | assert(!f1.equals(f4)) 63 | // hashcode is broken on the JVM 64 | assertEquals(f1.hashCode(), f4.hashCode()) 65 | 66 | f2.setBeginIndex(1) 67 | assert(!f1.equals(f2)) 68 | assert(f1.hashCode() != f2.hashCode()) 69 | 70 | val f5 = new FieldPosition(field, 1) 71 | f5.setEndIndex(1) 72 | assert(!f1.equals(f5)) 73 | assert(f1.hashCode() != f5.hashCode()) 74 | } 75 | 76 | test("to_string") { 77 | val field = TestField("abc") 78 | val f1 = new FieldPosition(field, 1) 79 | assertEquals( 80 | "java.text.FieldPosition[field=1,attribute=testsuite.javalib.text.TestField(abc),beginIndex=0,endIndex=0]", 81 | f1.toString 82 | ) // scalastyle:off 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/text/NormalizerTest.scala: -------------------------------------------------------------------------------- 1 | // Copyright 2013 the V8 project authors. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are 4 | // met: 5 | // 6 | // * Redistributions of source code must retain the above copyright 7 | // notice, this list of conditions and the following disclaimer. 8 | // * Redistributions in binary form must reproduce the above 9 | // copyright notice, this list of conditions and the following 10 | // disclaimer in the documentation and/or other materials provided 11 | // with the distribution. 12 | // * Neither the name of Google Inc. nor the names of its 13 | // contributors may be used to endorse or promote products derived 14 | // from this software without specific prior written permission. 15 | // 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | package testsuite.javalib.text 29 | 30 | import java.text.Normalizer 31 | import java.text.Normalizer.Form 32 | 33 | /** Adapted from https://github.com/v8/v8/blob/lkgr/test/intl/string/normalization.js 34 | */ 35 | class NormalizerTest extends munit.FunSuite { 36 | import Normalizer.normalize 37 | import Form._ 38 | 39 | // Common use case when searching for "not very exact" match. 40 | // These are examples of data one might encounter in real use. 41 | test("real use cases") { 42 | // Vietnamese legacy text, old Windows 9x / non-Unicode applications use 43 | // windows-1258 code page, which is neither precomposed, nor decomposed. 44 | assertEquals(normalize("ti\u00ea\u0301ng Vi\u00ea\u0323t", NFKD), 45 | normalize("ti\u1ebfng Vi\u1ec7t", NFKD) 46 | ) // all precomposed 47 | 48 | // Various kinds of spaces 49 | assertEquals(normalize("Google\u0020Maps", NFKD), // normal space 50 | normalize("Google\u00a0Maps", NFKD) 51 | ) // non-breaking space 52 | assertEquals(normalize("Google\u0020Maps", NFKD), // normal space 53 | normalize("Google\u2002Maps", NFKD) 54 | ) // en-space 55 | assertEquals(normalize("Google\u0020Maps", NFKD), // normal space 56 | normalize("Google\u2003Maps", NFKD) 57 | ) // em-space 58 | assertEquals(normalize("Google\u0020Maps", NFKD), // normal space 59 | normalize("Google\u3000Maps", NFKC) 60 | ) // ideographic space 61 | 62 | // Latin small ligature "fi" 63 | assertEquals(normalize("fi", NFKD), normalize("\ufb01", NFKD)) 64 | 65 | // ŀ, Latin small L with middle dot, used in Catalan and often represented 66 | // as decomposed for non-Unicode environments ( l + ·) 67 | assertEquals(normalize("l\u00b7", NFKD), normalize("\u0140", NFKD)) 68 | 69 | // Legacy text, Japanese narrow Kana (MS-DOS & Win 3.x time) 70 | assertEquals(normalize("\u30d1\u30bd\u30b3\u30f3", NFKD), // パソコン : wide 71 | normalize("\uff8a\uff9f\uff7f\uff7a\uff9d", NFKD) 72 | ) // パソコン : narrow 73 | // Also for Japanese, Latin fullwidth forms vs. ASCII 74 | assertEquals(normalize("ABCD", NFKD), 75 | normalize("\uff21\uff22\uff23\uff24", NFKD) 76 | ) // ABCD, fullwidth 77 | } 78 | 79 | test("edge cases") { 80 | intercept[NullPointerException](normalize("ABC", null)) 81 | intercept[NullPointerException](normalize(null, NFKD)) 82 | intercept[NullPointerException](normalize(null, null)) 83 | } 84 | 85 | // Several kinds of mappings. No need to be comprehensive, we don't test 86 | // the ICU functionality, we only test C - JavaScript 'glue' 87 | val testData = Array( 88 | // org, default, NFC, NFD, NKFC, NKFD 89 | Array("\u00c7", // Ç : Combining sequence, Latin 1 90 | "\u00c7", 91 | "\u0043\u0327", 92 | "\u00c7", 93 | "\u0043\u0327" 94 | ), 95 | Array("\u0218", // Ș : Combining sequence, non-Latin 1 96 | "\u0218", 97 | "\u0053\u0326", 98 | "\u0218", 99 | "\u0053\u0326" 100 | ), 101 | Array("\uac00", // 가 : Hangul 102 | "\uac00", 103 | "\u1100\u1161", 104 | "\uac00", 105 | "\u1100\u1161" 106 | ), 107 | Array("\uff76", // カ : Narrow Kana 108 | "\uff76", 109 | "\uff76", 110 | "\u30ab", 111 | "\u30ab" 112 | ), 113 | Array("\u00bc", // ¼ : Fractions 114 | "\u00bc", 115 | "\u00bc", 116 | "\u0031\u2044\u0034", 117 | "\u0031\u2044\u0034" 118 | ), 119 | Array("\u01c6", // dž : Latin ligature 120 | "\u01c6", 121 | "\u01c6", 122 | "\u0064\u017e", 123 | "\u0064\u007a\u030c" 124 | ), 125 | Array("s\u0307\u0323", // s + dot above + dot below, ordering of combining marks 126 | "\u1e69", 127 | "s\u0323\u0307", 128 | "\u1e69", 129 | "s\u0323\u0307" 130 | ), 131 | Array("\u3300", // ㌀ : Squared characters 132 | "\u3300", 133 | "\u3300", 134 | "\u30a2\u30d1\u30fc\u30c8", // アパート 135 | "\u30a2\u30cf\u309a\u30fc\u30c8" 136 | ), // アパート 137 | Array("\ufe37", // ︷ : Vertical forms 138 | "\ufe37", 139 | "\ufe37", 140 | "{", 141 | "{" 142 | ), 143 | Array("\u2079", // ⁹ : superscript 9 144 | "\u2079", 145 | "\u2079", 146 | "9", 147 | "9" 148 | ), 149 | Array( 150 | "\ufee5\ufee6\ufee7\ufee8", // Arabic forms 151 | "\ufee5\ufee6\ufee7\ufee8", 152 | "\ufee5\ufee6\ufee7\ufee8", 153 | "\u0646\u0646\u0646\u0646", 154 | "\u0646\u0646\u0646\u0646" 155 | ), 156 | Array("\u2460", // ① : Circled 157 | "\u2460", 158 | "\u2460", 159 | "1", 160 | "1" 161 | ), 162 | Array("\u210c", // ℌ : Font variants 163 | "\u210c", 164 | "\u210c", 165 | "H", 166 | "H" 167 | ), 168 | Array("\u2126", // Ω : Singleton, OHM sign vs. Greek capital letter OMEGA 169 | "\u03a9", 170 | "\u03a9", 171 | "\u03a9", 172 | "\u03a9" 173 | ), 174 | Array( 175 | "\ufdfb", // Long ligature, ARABIC LIGATURE JALLAJALALOUHOU 176 | "\ufdfb", 177 | "\ufdfb", 178 | "\u062C\u0644\u0020\u062C\u0644\u0627\u0644\u0647", 179 | "\u062C\u0644\u0020\u062C\u0644\u0627\u0644\u0647" 180 | ) 181 | ) 182 | 183 | test("array") { 184 | val kNFC = 1 185 | val kNFD = 2 186 | val kNFKC = 3 187 | val kNFKD = 4 188 | for (i <- testData.indices) 189 | // the original, NFC and NFD should normalize to the same thing 190 | for (column <- 0 until 3) { 191 | val str = testData(i)(column) 192 | assertEquals(normalize(str, NFC), testData(i)(kNFC)) 193 | assertEquals(normalize(str, NFD), testData(i)(kNFD)) 194 | assertEquals(normalize(str, NFKC), testData(i)(kNFKC)) 195 | assertEquals(normalize(str, NFKD), testData(i)(kNFKD)) 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/text/ParsePositionTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.text 2 | 3 | import java.text.ParsePosition 4 | 5 | class ParsePositionTest extends munit.FunSuite { 6 | test("constructors") { 7 | val p1 = new ParsePosition(1) 8 | assertEquals(1, p1.getIndex()) 9 | assertEquals(-1, p1.getErrorIndex()) 10 | } 11 | 12 | test("setters") { 13 | val p1 = new ParsePosition(1) 14 | assertEquals(1, p1.getIndex()) 15 | assertEquals(-1, p1.getErrorIndex()) 16 | 17 | p1.setIndex(2) 18 | p1.setErrorIndex(2) 19 | 20 | assertEquals(2, p1.getIndex()) 21 | assertEquals(2, p1.getErrorIndex()) 22 | } 23 | 24 | test("equals_hash_code") { 25 | val p1 = new ParsePosition(1) 26 | assertEquals(p1, p1) 27 | assertEquals(p1.hashCode(), p1.hashCode()) 28 | 29 | val p2 = new ParsePosition(1) 30 | assertEquals(p1, p2) 31 | assertEquals(p1.hashCode(), p2.hashCode()) 32 | 33 | val p3 = new ParsePosition(2) 34 | assert(!p1.equals(p3)) 35 | assert(p1.hashCode() != p3.hashCode()) 36 | 37 | p1.setIndex(4) 38 | assert(!p1.equals(p2)) 39 | assert(p1.hashCode() != p2.hashCode()) 40 | 41 | val p5 = new ParsePosition(1) 42 | val p6 = new ParsePosition(1) 43 | p5.setErrorIndex(1) 44 | assert(!p5.equals(p6)) 45 | assert(p5.hashCode() != p6.hashCode()) 46 | } 47 | 48 | test("to_string") { 49 | val p1 = new ParsePosition(1) 50 | assertEquals("java.text.ParsePosition[index=1,errorIndex=-1]", p1.toString) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/util/CurrencyTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.util 2 | 3 | import java.util.{ Currency, Locale } 4 | import testsuite.utils.Platform 5 | 6 | class CurrencyTest extends munit.FunSuite { 7 | case class CurrencyTestResults( 8 | expectedCurrencyCode: String, 9 | expectedNumericCode: Int, 10 | expectedFractionDigits: Int, 11 | expectedSymbol: String, 12 | expectedDisplayName: String 13 | ) 14 | 15 | // Have separate js vs jvm results to allow for CLDR differences 16 | trait CombinedCurrencyTestResults { 17 | def jsNativeResults: CurrencyTestResults 18 | def jvmResults: CurrencyTestResults 19 | } 20 | 21 | case class LocaleCurrencyTest( 22 | locale: Locale, 23 | jsNativeResults: CurrencyTestResults, 24 | jvmResults: CurrencyTestResults 25 | ) extends CombinedCurrencyTestResults 26 | object LocaleCurrencyTest { 27 | def apply(locale: Locale, results: CurrencyTestResults): LocaleCurrencyTest = 28 | LocaleCurrencyTest(locale, results, results) 29 | } 30 | 31 | case class CodeCurrencyTest( 32 | currencyCode: String, 33 | jsNativeResults: CurrencyTestResults, 34 | jvmResults: CurrencyTestResults 35 | ) extends CombinedCurrencyTestResults 36 | object CodeCurrencyTest { 37 | def apply(currencyCode: String, results: CurrencyTestResults): CodeCurrencyTest = 38 | CodeCurrencyTest(currencyCode, results, results) 39 | } 40 | 41 | case class DefaultLocaleCurrencyTest(defaultLocale: Locale, tests: Seq[CodeCurrencyTest]) 42 | 43 | // Given a locale, lookup a currency code for it & test values 44 | private val localeCurrencyTests: Seq[LocaleCurrencyTest] = Seq( 45 | LocaleCurrencyTest( 46 | Locale.CANADA, 47 | results = CurrencyTestResults("CAD", 124, 2, "$", "Canadian Dollar") 48 | ), 49 | LocaleCurrencyTest( 50 | Locale.CANADA_FRENCH, 51 | jsNativeResults = CurrencyTestResults("CAD", 124, 2, "$", "dollar canadien"), 52 | jvmResults = CurrencyTestResults("CAD", 124, 2, "$ CA", "dollar canadien") 53 | ), 54 | LocaleCurrencyTest( 55 | Locale.CHINA, 56 | jsNativeResults = CurrencyTestResults("CNY", 156, 2, "¥", "人民币"), 57 | jvmResults = CurrencyTestResults("CNY", 156, 2, "¥", "人民币") 58 | ), 59 | LocaleCurrencyTest( 60 | Locale.FRANCE, 61 | results = CurrencyTestResults("EUR", 978, 2, "€", "euro") 62 | ), 63 | LocaleCurrencyTest( 64 | Locale.GERMANY, 65 | results = CurrencyTestResults("EUR", 978, 2, "€", "Euro") 66 | ), 67 | LocaleCurrencyTest( 68 | Locale.ITALY, 69 | jsNativeResults = CurrencyTestResults("EUR", 978, 2, "€", "euro"), 70 | jvmResults = CurrencyTestResults("EUR", 978, 2, "€", "euro") 71 | ), 72 | LocaleCurrencyTest( 73 | Locale.JAPAN, 74 | results = CurrencyTestResults("JPY", 392, 0, "¥", "日本円") 75 | ), 76 | LocaleCurrencyTest( 77 | Locale.KOREA, 78 | results = CurrencyTestResults("KRW", 410, 0, "₩", "대한민국 원") 79 | ), 80 | LocaleCurrencyTest( 81 | Locale.PRC, 82 | jsNativeResults = CurrencyTestResults("CNY", 156, 2, "¥", "人民币"), 83 | jvmResults = CurrencyTestResults("CNY", 156, 2, "¥", "人民币") 84 | ), 85 | LocaleCurrencyTest( 86 | Locale.TAIWAN, 87 | jsNativeResults = CurrencyTestResults("TWD", 901, 2, "$", "新台幣"), 88 | jvmResults = CurrencyTestResults("TWD", 901, 2, "$", "新台幣") 89 | ), 90 | LocaleCurrencyTest( 91 | Locale.UK, 92 | jsNativeResults = CurrencyTestResults("GBP", 826, 2, "£", "British Pound"), 93 | jvmResults = CurrencyTestResults("GBP", 826, 2, "£", "British Pound") 94 | ), 95 | LocaleCurrencyTest( 96 | Locale.US, 97 | jsNativeResults = CurrencyTestResults("USD", 840, 2, "$", "US Dollar"), 98 | jvmResults = CurrencyTestResults("USD", 840, 2, "$", "US Dollar") 99 | ) 100 | ) 101 | 102 | // Given a default locale, lookup other currencies by code & compare values 103 | private val defaultLocaleCurrencyTests: Seq[DefaultLocaleCurrencyTest] = Seq( 104 | DefaultLocaleCurrencyTest( 105 | defaultLocale = Locale.US, 106 | tests = Seq( 107 | CodeCurrencyTest( 108 | currencyCode = "CAD", 109 | jsNativeResults = CurrencyTestResults("CAD", 124, 2, "CA$", "Canadian Dollar"), 110 | jvmResults = CurrencyTestResults("CAD", 124, 2, "CA$", "Canadian Dollar") 111 | ), 112 | CodeCurrencyTest( 113 | currencyCode = "EUR", 114 | jsNativeResults = CurrencyTestResults("EUR", 978, 2, "€", "Euro"), 115 | jvmResults = CurrencyTestResults("EUR", 978, 2, "€", "Euro") 116 | ), 117 | CodeCurrencyTest( 118 | currencyCode = "JPY", 119 | jsNativeResults = CurrencyTestResults("JPY", 392, 0, "¥", "Japanese Yen"), 120 | jvmResults = CurrencyTestResults("JPY", 392, 0, "¥", "Japanese Yen") 121 | ), 122 | CodeCurrencyTest( 123 | currencyCode = "KRW", 124 | jsNativeResults = CurrencyTestResults("KRW", 410, 0, "₩", "South Korean Won"), 125 | jvmResults = CurrencyTestResults("KRW", 410, 0, "₩", "South Korean Won") 126 | ), 127 | CodeCurrencyTest( 128 | currencyCode = "CNY", 129 | jsNativeResults = CurrencyTestResults("CNY", 156, 2, "CN¥", "Chinese Yuan"), 130 | jvmResults = CurrencyTestResults("CNY", 156, 2, "CN¥", "Chinese Yuan") 131 | ), 132 | CodeCurrencyTest( 133 | currencyCode = "TWD", 134 | jsNativeResults = CurrencyTestResults("TWD", 901, 2, "NT$", "New Taiwan Dollar"), 135 | jvmResults = CurrencyTestResults("TWD", 901, 2, "NT$", "New Taiwan Dollar") 136 | ), 137 | CodeCurrencyTest( 138 | currencyCode = "GBP", 139 | jsNativeResults = CurrencyTestResults("GBP", 826, 2, "£", "British Pound"), 140 | jvmResults = CurrencyTestResults("GBP", 826, 2, "£", "British Pound") 141 | ), 142 | CodeCurrencyTest( 143 | currencyCode = "USD", 144 | jsNativeResults = CurrencyTestResults("USD", 840, 2, "$", "US Dollar"), 145 | jvmResults = CurrencyTestResults("USD", 840, 2, "$", "US Dollar") 146 | ) 147 | ) 148 | ), 149 | DefaultLocaleCurrencyTest( 150 | defaultLocale = Locale.GERMANY, 151 | tests = Seq( 152 | CodeCurrencyTest( 153 | currencyCode = "CAD", 154 | results = CurrencyTestResults("CAD", 124, 2, "CA$", "Kanadischer Dollar") 155 | ), 156 | CodeCurrencyTest( 157 | currencyCode = "EUR", 158 | results = CurrencyTestResults("EUR", 978, 2, "€", "Euro") 159 | ), 160 | CodeCurrencyTest( 161 | currencyCode = "JPY", 162 | jsNativeResults = CurrencyTestResults("JPY", 392, 0, "¥", "Japanischer Yen"), 163 | jvmResults = CurrencyTestResults("JPY", 392, 0, "¥", "Japanischer Yen") 164 | ), 165 | CodeCurrencyTest( 166 | currencyCode = "KRW", 167 | results = CurrencyTestResults("KRW", 410, 0, "₩", "Südkoreanischer Won") 168 | ), 169 | CodeCurrencyTest( 170 | currencyCode = "CNY", 171 | results = CurrencyTestResults("CNY", 156, 2, "CN¥", "Renminbi Yuan") 172 | ), 173 | CodeCurrencyTest( 174 | currencyCode = "TWD", 175 | results = CurrencyTestResults("TWD", 901, 2, "NT$", "Neuer Taiwan-Dollar") 176 | ), 177 | CodeCurrencyTest( 178 | currencyCode = "GBP", 179 | jsNativeResults = CurrencyTestResults("GBP", 826, 2, "£", "Britisches Pfund"), 180 | jvmResults = CurrencyTestResults("GBP", 826, 2, "£", "Britisches Pfund") 181 | ), 182 | CodeCurrencyTest( 183 | currencyCode = "USD", 184 | results = CurrencyTestResults("USD", 840, 2, "$", "US-Dollar") 185 | ) 186 | ) 187 | ), 188 | DefaultLocaleCurrencyTest( 189 | defaultLocale = Locale.CHINA, 190 | tests = Seq( 191 | CodeCurrencyTest( 192 | currencyCode = "CAD", 193 | results = CurrencyTestResults("CAD", 124, 2, "CA$", "加拿大元") 194 | ), 195 | CodeCurrencyTest( 196 | currencyCode = "EUR", 197 | results = CurrencyTestResults("EUR", 978, 2, "€", "欧元") 198 | ), 199 | CodeCurrencyTest( 200 | currencyCode = "JPY", 201 | results = CurrencyTestResults("JPY", 392, 0, "JP¥", "日元") 202 | ), 203 | CodeCurrencyTest( 204 | currencyCode = "KRW", 205 | jsNativeResults = CurrencyTestResults("KRW", 410, 0, "₩", "韩元"), 206 | jvmResults = CurrencyTestResults("KRW", 410, 0, "₩", "韩元") 207 | ), 208 | CodeCurrencyTest( 209 | currencyCode = "CNY", 210 | jsNativeResults = CurrencyTestResults("CNY", 156, 2, "¥", "人民币"), 211 | jvmResults = CurrencyTestResults("CNY", 156, 2, "¥", "人民币") 212 | ), 213 | CodeCurrencyTest( 214 | currencyCode = "TWD", 215 | results = CurrencyTestResults("TWD", 901, 2, "NT$", "新台币") 216 | ), 217 | CodeCurrencyTest( 218 | currencyCode = "GBP", 219 | results = CurrencyTestResults("GBP", 826, 2, "£", "英镑") 220 | ), 221 | CodeCurrencyTest( 222 | currencyCode = "USD", 223 | results = CurrencyTestResults("USD", 840, 2, "US$", "美元") 224 | ) 225 | ) 226 | ) 227 | ) 228 | 229 | private def testCurrency(currency: Currency, expectedResults: CurrencyTestResults): Unit = { 230 | import expectedResults._ 231 | 232 | assertEquals(expectedCurrencyCode, currency.getCurrencyCode) 233 | assertEquals(expectedNumericCode, currency.getNumericCode) 234 | assertEquals(expectedFractionDigits, currency.getDefaultFractionDigits) 235 | assertEquals(expectedSymbol, currency.getSymbol) 236 | assertEquals(expectedDisplayName, currency.getDisplayName) 237 | } 238 | 239 | protected def test_standard_locales( 240 | f: CombinedCurrencyTestResults => CurrencyTestResults 241 | ): Unit = { 242 | // Basic test, get a locale's currency, and test results 243 | localeCurrencyTests.foreach { (test: LocaleCurrencyTest) => 244 | // Even when you get a currency for a specific locale, description calls (getSymbol and getDisplayName) 245 | // return strings will be based upon the default locale, so lets set the default locale to the test locale 246 | Locale.setDefault(test.locale) 247 | val localeCurrency = Currency.getInstance(test.locale) 248 | testCurrency(localeCurrency, f(test)) 249 | } 250 | 251 | // Set a default locale, then lookup multiple currencies by code and test results 252 | defaultLocaleCurrencyTests.foreach { (defaultTest: DefaultLocaleCurrencyTest) => 253 | Locale.setDefault(defaultTest.defaultLocale) 254 | defaultTest.tests.foreach { (test: CodeCurrencyTest) => 255 | val codeCurrency = Currency.getInstance(test.currencyCode) 256 | testCurrency(codeCurrency, f(test)) 257 | } 258 | } 259 | } 260 | 261 | test("available_currencies") { 262 | assert(Currency.getAvailableCurrencies().size() > 0) 263 | } 264 | 265 | test("standard_locales") { 266 | assume(!sys.props.get("java.version").exists(_.startsWith("1.8."))) 267 | if (Platform.executingInJVM) { 268 | test_standard_locales(_.jvmResults) 269 | } else { 270 | test_standard_locales(_.jsNativeResults) 271 | } 272 | } 273 | 274 | override def munitIgnore: Boolean = Platform.executingInScalaNative 275 | } 276 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/util/LocaleBuilderTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.util 2 | 3 | import java.util.{ IllformedLocaleException, Locale } 4 | 5 | import testsuite.utils.Platform 6 | 7 | class LocaleBuilderTest extends munit.FunSuite { 8 | test("build_with_language") { 9 | val b1 = new Locale.Builder() 10 | val locale1 = b1.setLanguage("en").build 11 | assertEquals("en", locale1.getLanguage) 12 | 13 | // Null should reset 14 | val b2 = new Locale.Builder() 15 | val locale2 = b2.setLanguage(null).build 16 | assertEquals("", locale2.getLanguage) 17 | 18 | val b3 = new Locale.Builder() 19 | val locale3 = b3.setLanguage("en").setLanguage("").build 20 | assertEquals("", locale3.getLanguage) 21 | 22 | // Check for compliance 23 | intercept[IllformedLocaleException]( 24 | new Locale.Builder().setLanguage("toolongtobevalid") 25 | ) 26 | } 27 | 28 | test("build_language_canonicalization") { 29 | val b1 = new Locale.Builder() 30 | val locale = b1.setLanguage("En").build 31 | assertEquals("en", locale.getLanguage) 32 | } 33 | 34 | test("build_with_script") { 35 | val b1 = new Locale.Builder() 36 | val locale = b1.setScript("Cyrl").build 37 | assertEquals("Cyrl", locale.getScript) 38 | 39 | // null resets 40 | val b2 = new Locale.Builder() 41 | val locale2 = b2.setScript(null).build 42 | assertEquals("", locale2.getScript) 43 | 44 | val b3 = new Locale.Builder() 45 | val locale3 = b3.setScript("Cyrl").setScript("").build 46 | assertEquals("", locale3.getScript) 47 | 48 | // Check for compliance, too long 49 | intercept[IllformedLocaleException]( 50 | new Locale.Builder().setScript("toolongtobevalid") 51 | ) 52 | 53 | // Check for compliance, too short 54 | intercept[IllformedLocaleException](new Locale.Builder().setScript("ts")) 55 | } 56 | 57 | test("build_script_canonicalization") { 58 | val b1 = new Locale.Builder() 59 | val locale = b1.setScript("cyrl").build 60 | assertEquals("Cyrl", locale.getScript) 61 | } 62 | 63 | test("build_with_region") { 64 | val b1 = new Locale.Builder() 65 | val locale = b1.setRegion("US").build 66 | assertEquals("US", locale.getCountry) 67 | 68 | // null resets 69 | val b2 = new Locale.Builder() 70 | val locale2 = b2.setRegion(null).build 71 | assertEquals("", locale2.getCountry) 72 | 73 | val b3 = new Locale.Builder() 74 | val locale3 = b3.setRegion("US").setRegion("").build 75 | assertEquals("", locale3.getCountry) 76 | 77 | val b4 = new Locale.Builder() 78 | val locale4 = b4.setRegion("029").build 79 | assertEquals("029", locale4.getCountry) 80 | 81 | // Check for compliance, too long 82 | intercept[IllformedLocaleException]( 83 | new Locale.Builder().setRegion("toolongtobevalid") 84 | ) 85 | intercept[IllformedLocaleException](new Locale.Builder().setRegion("1234")) 86 | 87 | // Check for compliance, too short 88 | intercept[IllformedLocaleException](new Locale.Builder().setRegion("t")) 89 | intercept[IllformedLocaleException](new Locale.Builder().setRegion("1")) 90 | } 91 | 92 | test("build_with_variant") { 93 | val b1 = new Locale.Builder() 94 | val locale = b1.setVariant("polyton").build 95 | assertEquals("polyton", locale.getVariant) 96 | 97 | // null resets 98 | val b2 = new Locale.Builder() 99 | val locale2 = b2.setVariant(null).build 100 | assertEquals("", locale2.getVariant) 101 | 102 | // Some examples taken from IANA Subtag registry 103 | val cases = List("1606nict", "1901", "baku1926", "fonxsamp", "luna1918") 104 | cases.foreach { v => 105 | val b = new Locale.Builder() 106 | val locale = b.setVariant(v).build 107 | assertEquals(v, locale.getVariant) 108 | } 109 | 110 | // Multiple variants are allowed 111 | cases.zip(cases).foreach { case (c1, c2) => 112 | val b = new Locale.Builder() 113 | val locale1 = b.setVariant(s"$c1-$c2").build 114 | assertEquals(s"${c1}_$c2", locale1.getVariant) 115 | 116 | val locale2 = b.setVariant(s"${c1}_$c2").build 117 | assertEquals(s"${c1}_$c2", locale2.getVariant) 118 | } 119 | 120 | val b4 = new Locale.Builder() 121 | val locale4 = b4.setVariant("VALENCIA").build 122 | assertEquals("VALENCIA", locale4.getVariant) 123 | 124 | // Check for compliance, too long 125 | intercept[IllformedLocaleException](new Locale.Builder().setVariant("four")) 126 | intercept[IllformedLocaleException](new Locale.Builder().setVariant("abcde!")) 127 | 128 | // Check for compliance, too short 129 | intercept[IllformedLocaleException](new Locale.Builder().setVariant("t")) 130 | intercept[IllformedLocaleException](new Locale.Builder().setVariant("1")) 131 | } 132 | 133 | test("build_with_extensions") { 134 | val b1 = new Locale.Builder() 135 | val locale = b1.setExtension('a', "ca-japanese").build 136 | assertEquals("ca-japanese", locale.getExtension('a')) 137 | 138 | // null resets 139 | val b2 = new Locale.Builder() 140 | val locale2 = 141 | b2.setExtension('a', "ca-japanese").setExtension('a', "").build 142 | assert(null == locale2.getExtension('a')) 143 | 144 | // Check for compliance on the keys 145 | intercept[IllformedLocaleException](new Locale.Builder().setExtension('!', "abc")) 146 | 147 | // Check for compliance on the value 148 | intercept[IllformedLocaleException](new Locale.Builder().setExtension('a', "a")) 149 | intercept[IllformedLocaleException]( 150 | new Locale.Builder().setExtension('a', "abcdefghi") 151 | ) 152 | } 153 | 154 | test("extensions_canonalization") { 155 | val b1 = new Locale.Builder() 156 | val locale = b1.setExtension('a', "Ca-Japanese").build 157 | assertEquals("ca-japanese", locale.getExtension('a')) 158 | } 159 | 160 | test("build_with_unicode_extensions") { 161 | val b1 = new Locale.Builder() 162 | val locale = b1 163 | .setUnicodeLocaleKeyword("nu", "thai") 164 | .setUnicodeLocaleKeyword("ok", "") 165 | .build 166 | assert(locale.getUnicodeLocaleKeys.contains("nu")) 167 | assert(locale.getUnicodeLocaleKeys.contains("ok")) 168 | assertEquals("thai", locale.getUnicodeLocaleType("nu")) 169 | assertEquals("", locale.getUnicodeLocaleType("ok")) 170 | assert(null == locale.getUnicodeLocaleType("ko")) 171 | 172 | // Check that the extensions is propagated to the general extension 173 | assert(locale.getExtensionKeys.contains(Locale.UNICODE_LOCALE_EXTENSION)) 174 | assertEquals("nu-thai-ok", locale.getExtension(Locale.UNICODE_LOCALE_EXTENSION)) 175 | 176 | // Check for compliance on the keys 177 | intercept[NullPointerException]( 178 | new Locale.Builder().setUnicodeLocaleKeyword(null, "thai") 179 | ) 180 | // key too short 181 | intercept[IllformedLocaleException]( 182 | new Locale.Builder().setUnicodeLocaleKeyword("a", "thai") 183 | ) 184 | // value too short 185 | intercept[IllformedLocaleException]( 186 | new Locale.Builder().setUnicodeLocaleKeyword("nu", "th") 187 | ) 188 | // value too long 189 | intercept[IllformedLocaleException]( 190 | new Locale.Builder().setUnicodeLocaleKeyword("nu", "toolongvalue") 191 | ) 192 | } 193 | 194 | test("replace_unicode_extensions") { 195 | val b1 = new Locale.Builder() 196 | val locale1 = b1 197 | .setUnicodeLocaleKeyword("nu", "thai") 198 | .setUnicodeLocaleKeyword("ok", "") 199 | .build 200 | assertEquals("thai", locale1.getUnicodeLocaleType("nu")) 201 | val locale2 = 202 | b1.setExtension(Locale.UNICODE_LOCALE_EXTENSION, "newvalue").build 203 | assert(locale2.getUnicodeLocaleKeys.isEmpty) 204 | assert(null == locale2.getUnicodeLocaleType("nu")) 205 | 206 | // Check that the extensions is propagated to the general extension 207 | assert(locale2.getExtensionKeys.contains(Locale.UNICODE_LOCALE_EXTENSION)) 208 | assertEquals("newvalue", locale2.getExtension(Locale.UNICODE_LOCALE_EXTENSION)) 209 | } 210 | 211 | test("build_with_unicode_attributes") { 212 | val b1 = new Locale.Builder() 213 | val locale1 = b1.addUnicodeLocaleAttribute("attr").build 214 | assert(locale1.getUnicodeLocaleAttributes.contains("attr")) 215 | 216 | // Check for compliance on the attribute 217 | intercept[NullPointerException](new Locale.Builder().addUnicodeLocaleAttribute(null)) 218 | intercept[IllformedLocaleException]( 219 | new Locale.Builder().addUnicodeLocaleAttribute("toolongvalue") 220 | ) 221 | } 222 | 223 | test("remove_unicode_attribute") { 224 | assume(!sys.props.get("java.version").exists(_.startsWith("1.8."))) 225 | val b1 = new Locale.Builder() 226 | val locale1 = b1 227 | .addUnicodeLocaleAttribute("attr") 228 | .removeUnicodeLocaleAttribute("attr") 229 | .build 230 | assert(locale1.getUnicodeLocaleAttributes.isEmpty) 231 | 232 | // Check for compliance on the attribute 233 | if (Platform.executingInJVM) 234 | intercept[NullPointerException]( 235 | new Locale.Builder().removeUnicodeLocaleAttribute(null) 236 | ) 237 | else 238 | // Scala.js follows the spec 239 | intercept[NullPointerException](new Locale.Builder().removeUnicodeLocaleAttribute(null)) 240 | intercept[IllformedLocaleException]( 241 | new Locale.Builder().removeUnicodeLocaleAttribute("toolongvalue") 242 | ) 243 | } 244 | 245 | test("clear") { 246 | val b = new Locale.Builder() 247 | val locale1 = b.setLanguage("fi").setRegion("FI").setVariant("POSIX").build 248 | assertEquals("fi", locale1.getLanguage) 249 | assertEquals("FI", locale1.getCountry) 250 | assertEquals("POSIX", locale1.getVariant) 251 | 252 | val locale2 = b.clear().build 253 | assertEquals("", locale2.getLanguage) 254 | assertEquals("", locale2.getCountry) 255 | assertEquals("", locale2.getVariant) 256 | } 257 | 258 | test("clear_extensions") { 259 | val b = new Locale.Builder() 260 | val locale1 = b 261 | .setLanguage("es") 262 | .setRegion("CL") 263 | .setExtension('a', "ca-japanese") 264 | .build 265 | assertEquals("ca-japanese", locale1.getExtension('a')) 266 | assertEquals("es", locale1.getLanguage) 267 | assertEquals("CL", locale1.getCountry) 268 | val locale2 = b.clearExtensions().build 269 | assert(null == locale2.getExtension('a')) 270 | assertEquals("es", locale1.getLanguage) 271 | assertEquals("CL", locale1.getCountry) 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/javalib/util/LocaleCategoryTest.scala: -------------------------------------------------------------------------------- 1 | package testsuite.javalib.util 2 | 3 | import java.util.Locale 4 | 5 | class LocaleCategoryTest extends munit.FunSuite { 6 | import Locale.Category 7 | 8 | test("getOrdinal") { 9 | assertEquals(0, Category.DISPLAY.ordinal) 10 | assertEquals(1, Category.FORMAT.ordinal) 11 | } 12 | 13 | test("getValues") { 14 | assertEquals(2, Category.values().length) 15 | assertEquals(Category.DISPLAY, Category.values()(0)) 16 | assertEquals(Category.FORMAT, Category.values()(1)) 17 | } 18 | 19 | test("valueOf") { 20 | assertEquals(Category.DISPLAY, Category.valueOf("DISPLAY")) 21 | assertEquals(Category.FORMAT, Category.valueOf("FORMAT")) 22 | 23 | intercept[IllegalArgumentException](Category.valueOf("")) 24 | intercept[RuntimeException](Category.valueOf(null)) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /tests/shared/src/test/scala/testsuite/utils/Platform.scala: -------------------------------------------------------------------------------- 1 | /* __ *\ 2 | ** ________ ___ / / ___ __ ____ Scala.js Test Suite ** 3 | ** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** 4 | ** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** 5 | ** /____/\___/_/ |_/____/_/ | |__/ /____/ ** 6 | ** |/____/ ** 7 | \* */ 8 | package testsuite.utils 9 | 10 | object Platform { 11 | 12 | /** Returns `true` if and only if the code is executing on a JVM. Note: Returns `false` when 13 | * executing on any other platform. 14 | */ 15 | final val executingInJVM = System.getProperty("java.vm.name") match { 16 | case "Scala Native" | "Scala.js" => false 17 | case _ => true 18 | } 19 | 20 | final val executingInScalaNative = System.getProperty("java.vm.name") == "Scala Native" 21 | 22 | } 23 | --------------------------------------------------------------------------------